@starbemtech/star-db-query-builder
Version:
A query builder to be used with mysql or postgres
1,451 lines (1,202 loc) • 30.3 kB
Markdown
# Star DB Query Builder
A powerful and flexible database query builder library for Node.js applications, supporting PostgreSQL and MySQL databases with TypeScript support.
## Table of Contents
- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [Database Initialization](#database-initialization)
- [Query Methods](#query-methods)
- [findFirst](#findfirst)
- [findMany](#findmany)
- [insert](#insert)
- [insertMany](#insertmany)
- [update](#update)
- [updateMany](#updatemany)
- [deleteOne](#deleteone)
- [deleteMany](#deletemany)
- [joins](#joins)
- [rawQuery](#rawquery)
- [Transactions](#transactions)
- [withTransaction](#withtransaction)
- [beginTransaction](#begintransaction)
- [Types and Interfaces](#types-and-interfaces)
- [Advanced Usage](#advanced-usage)
- [Monitoring](#monitoring)
- [Best Practices](#best-practices)
- [Error Handling](#error-handling)
- [Contributing](#contributing)
- [License](#license)
## ✨ Features
### 🔧 **Core Functionality**
- **🔄 Multi-Connection Support**: Connect simultaneously to multiple PostgreSQL and MySQL databases
- **🛡️ Type Safety**: Complete TypeScript support with strong typing
- **⚡ Auto Retry**: Automatic retry for transient errors (timeouts, lost connections)
- **📊 Monitoring**: Event system for monitoring and logging
- **🔍 Query Builder**: Fluent interface for building complex queries
- **📦 Batch Operations**: Optimized batch operations (insertMany, updateMany)
### 🗄️ **Database Support**
- **PostgreSQL**: Complete support with extensions (unaccent)
- **MySQL**: Full compatibility with MySQL 5.7+
- **Connection Pooling**: Efficient connection management
- **Transaction Support**: Full ACID transaction support with automatic rollback
- **Raw SQL**: Execute custom SQL queries when needed
### 🛠️ **Development Tools**
- **ESLint + Prettier**: Clean and consistent code
- **Jest**: Unit and integration tests
- **Husky**: Git hooks for code quality
- **TypeScript**: Compilation and typing
## 📦 Installation
```bash
npm install @starbemtech/star-db-query-builder
# or
pnpm add @starbemtech/star-db-query-builder
# or
yarn add @starbemtech/star-db-query-builder
```
## Quick Start
```typescript
import {
initDb,
getDbClient,
findFirst,
insert,
} from '@starbemtech/star-db-query-builder'
// Initialize database connection
await initDb({
type: 'pg', // or 'mysql'
options: {
host: 'localhost',
port: 5432,
database: 'myapp',
user: 'username',
password: 'password',
},
})
// Get database client
const dbClient = getDbClient()
// Find a user
const user = await findFirst({
tableName: 'users',
dbClient,
where: { email: { operator: '=', value: 'user@example.com' } },
})
// Insert a new user
const newUser = await insert({
tableName: 'users',
dbClient,
data: { name: 'John Doe', email: 'john@example.com' },
})
```
## Database Initialization
### initDb
Initializes a database connection with the specified configuration.
```typescript
await initDb({
name?: string, // Optional client name (default: 'default')
type: 'pg' | 'mysql', // Database type
options: PoolConfig | MySqlPoolOptions, // Connection options
retryOptions?: RetryOptions, // Optional retry configuration
installUnaccentExtension?: boolean // PostgreSQL unaccent extension
})
```
#### PostgreSQL Example
```typescript
import { initDb } from '@starbemtech/star-db-query-builder'
await initDb({
name: 'main',
type: 'pg',
options: {
host: 'localhost',
port: 5432,
database: 'myapp',
user: 'postgres',
password: 'password',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
},
retryOptions: {
retries: 3,
factor: 2,
minTimeout: 1000,
maxTimeout: 5000,
},
installUnaccentExtension: true,
})
```
#### MySQL Example
```typescript
import { initDb } from '@starbemtech/star-db-query-builder'
await initDb({
name: 'analytics',
type: 'mysql',
options: {
host: 'localhost',
port: 3306,
database: 'analytics',
user: 'root',
password: 'password',
connectionLimit: 10,
acquireTimeout: 60000,
timeout: 60000,
},
})
```
### getDbClient
Retrieves a database client by name.
```typescript
const dbClient = getDbClient(name?: string)
```
```typescript
// Get default client
const defaultClient = getDbClient()
// Get named client
const analyticsClient = getDbClient('analytics')
```
## Query Methods
### findFirst
Finds the first record that matches the specified conditions.
```typescript
const result = await findFirst<T>({
tableName: string,
dbClient: IDatabaseClient,
select?: string[],
where?: Conditions<T>,
groupBy?: string[],
orderBy?: OrderBy
})
```
#### Examples
```typescript
// Find user by email
const user = await findFirst({
tableName: 'users',
dbClient,
where: {
email: { operator: '=', value: 'user@example.com' },
},
})
// Find with specific fields
const user = await findFirst({
tableName: 'users',
dbClient,
select: ['id', 'name', 'email'],
where: {
status: { operator: '=', value: 'active' },
},
})
// Find with complex conditions
const user = await findFirst({
tableName: 'users',
dbClient,
where: {
AND: [
{ email: { operator: '=', value: 'user@example.com' } },
{ status: { operator: '=', value: 'active' } },
],
},
})
// Find with ordering
const latestUser = await findFirst({
tableName: 'users',
dbClient,
orderBy: [{ field: 'created_at', direction: 'DESC' }],
})
```
### findMany
Finds multiple records that match the specified conditions.
```typescript
const results = await findMany<T>({
tableName: string,
dbClient: IDatabaseClient,
select?: string[],
where?: Conditions<T>,
groupBy?: string[],
orderBy?: OrderBy,
limit?: number,
offset?: number,
unaccent?: boolean
})
```
#### Examples
```typescript
// Find all active users
const users = await findMany({
tableName: 'users',
dbClient,
where: {
status: { operator: '=', value: 'active' },
},
})
// Find with pagination
const users = await findMany({
tableName: 'users',
dbClient,
limit: 10,
offset: 20,
orderBy: [{ field: 'created_at', direction: 'DESC' }],
})
// Find with complex conditions
const users = await findMany({
tableName: 'users',
dbClient,
where: {
OR: [
{ status: { operator: '=', value: 'active' } },
{ status: { operator: '=', value: 'pending' } },
],
created_at: {
operator: '>=',
value: new Date('2023-01-01'),
},
},
})
// Find with grouping
const userStats = await findMany({
tableName: 'users',
dbClient,
select: ['status', 'COUNT(*) as count'],
groupBy: ['status'],
})
```
### insert
Inserts a single record into the database.
```typescript
const result = await insert<P, R>({
tableName: string,
dbClient: IDatabaseClient,
data: P,
returning?: string[]
})
```
#### Examples
```typescript
// Simple insert
const user = await insert({
tableName: 'users',
dbClient,
data: {
name: 'John Doe',
email: 'john@example.com',
age: 30,
},
})
// Insert with specific returning fields
const user = await insert({
tableName: 'users',
dbClient,
data: {
name: 'Jane Doe',
email: 'jane@example.com',
},
returning: ['id', 'name', 'email', 'created_at'],
})
// Insert with TypeScript typing
interface UserData {
name: string
email: string
age: number
}
interface User {
id: string
name: string
email: string
age: number
created_at: Date
updated_at: Date
}
const user: User = await insert<UserData, User>({
tableName: 'users',
dbClient,
data: {
name: 'John Doe',
email: 'john@example.com',
age: 30,
},
})
```
### insertMany
Inserts multiple records into the database in a single operation.
```typescript
const results = await insertMany<P, R>({
tableName: string,
dbClient: IDatabaseClient,
data: P[],
returning?: string[]
})
```
#### Examples
```typescript
// Insert multiple users
const users = await insertMany({
tableName: 'users',
dbClient,
data: [
{ name: 'John Doe', email: 'john@example.com' },
{ name: 'Jane Doe', email: 'jane@example.com' },
{ name: 'Bob Smith', email: 'bob@example.com' },
],
})
// Insert with returning fields
const users = await insertMany({
tableName: 'users',
dbClient,
data: [
{ name: 'John Doe', email: 'john@example.com' },
{ name: 'Jane Doe', email: 'jane@example.com' },
],
returning: ['id', 'name', 'email'],
})
```
### update
Updates a single record by ID.
```typescript
const result = await update<P, R>({
tableName: string,
dbClient: IDatabaseClient,
id: string,
data: P,
returning?: string[]
})
```
#### Examples
```typescript
// Simple update
const updatedUser = await update({
tableName: 'users',
dbClient,
id: 'user-123',
data: {
name: 'John Updated',
age: 31,
},
})
// Update with returning fields
const updatedUser = await update({
tableName: 'users',
dbClient,
id: 'user-123',
data: {
status: 'active',
last_login: new Date(),
},
returning: ['id', 'status', 'last_login', 'updated_at'],
})
```
### updateMany
Updates multiple records based on specified conditions.
```typescript
const results = await updateMany<P, R>({
tableName: string,
dbClient: IDatabaseClient,
data: P,
where: Conditions<T>,
returning?: string[]
})
```
#### Examples
```typescript
// Update all inactive users
const updatedUsers = await updateMany({
tableName: 'users',
dbClient,
data: {
status: 'active',
updated_at: new Date(),
},
where: {
status: { operator: '=', value: 'inactive' },
},
})
// Update with complex conditions
const updatedUsers = await updateMany({
tableName: 'users',
dbClient,
data: {
last_login: new Date(),
login_count: { operator: '+', value: 1 },
},
where: {
AND: [
{ status: { operator: '=', value: 'active' } },
{ last_login: { operator: '<', value: new Date('2023-01-01') } },
],
},
returning: ['id', 'name', 'last_login', 'login_count'],
})
```
### deleteOne
Deletes a single record by ID (soft delete by default).
```typescript
await deleteOne<T>({
tableName: string,
dbClient: IDatabaseClient,
id: string,
permanently?: boolean
})
```
#### Examples
```typescript
// Soft delete (sets status to 'deleted')
await deleteOne({
tableName: 'users',
dbClient,
id: 'user-123',
})
// Permanent delete
await deleteOne({
tableName: 'users',
dbClient,
id: 'user-123',
permanently: true,
})
```
### deleteMany
Deletes multiple records by IDs (soft delete by default).
```typescript
await deleteMany<T>({
tableName: string,
dbClient: IDatabaseClient,
ids: string[] | number[],
field?: string,
permanently?: boolean
})
```
#### Examples
```typescript
// Soft delete multiple users
await deleteMany({
tableName: 'users',
dbClient,
ids: ['user-1', 'user-2', 'user-3'],
})
// Permanent delete with custom field
await deleteMany({
tableName: 'orders',
dbClient,
ids: [1, 2, 3],
field: 'order_id',
permanently: true,
})
```
### joins
Executes queries with JOIN operations.
```typescript
const results = await joins<T>({
tableName: string,
dbClient: IDatabaseClient,
select: string[],
joins: JoinClause[],
where?: Conditions<T>,
groupBy?: string[],
orderBy?: OrderBy,
limit?: number,
offset?: number,
unaccent?: boolean
})
```
#### Examples
```typescript
// Simple JOIN
const usersWithOrders = await joins({
tableName: 'users',
dbClient,
select: ['users.id', 'users.name', 'orders.total'],
joins: [
{
type: 'LEFT',
table: 'orders',
on: 'users.id = orders.user_id',
},
],
where: {
'users.status': { operator: '=', value: 'active' },
},
})
// Multiple JOINs
const report = await joins({
tableName: 'users',
dbClient,
select: [
'users.name',
'users.email',
'COUNT(orders.id) as order_count',
'SUM(orders.total) as total_spent',
'plans.name as plan_name',
],
joins: [
{
type: 'LEFT',
table: 'orders',
on: 'users.id = orders.user_id',
},
{
type: 'LEFT',
table: 'user_plans',
on: 'users.id = user_plans.user_id',
},
{
type: 'LEFT',
table: 'plans',
on: 'user_plans.plan_id = plans.id',
},
],
groupBy: ['users.id', 'users.name', 'users.email', 'plans.name'],
having: {
'COUNT(orders.id)': { operator: '>', value: 0 },
},
orderBy: [{ field: 'total_spent', direction: 'DESC' }],
})
```
### rawQuery
Executes raw SQL queries directly on the database.
```typescript
const result = await rawQuery<T>({
dbClient: IDatabaseClient,
sql: string,
params?: any[]
})
```
#### Examples
```typescript
// Simple raw query
const users = await rawQuery({
dbClient,
sql: 'SELECT * FROM users WHERE active = true',
})
// Raw query with parameters
const user = await rawQuery({
dbClient,
sql: 'SELECT * FROM users WHERE id = ? AND email = ?',
params: ['user-123', 'user@example.com'],
})
// Complex aggregation
const stats = await rawQuery({
dbClient,
sql: `
SELECT
COUNT(*) as total_users,
AVG(age) as avg_age,
MAX(created_at) as last_created
FROM users
WHERE created_at >= ?
`,
params: [new Date('2023-01-01')],
})
```
## Transactions
Execute multiple database operations within a single transaction to ensure data consistency and atomicity.
### withTransaction
Executes a function within a database transaction with automatic commit/rollback handling.
```typescript
const result = await withTransaction<T>(
dbClient: IDatabaseClient,
transactionFn: (tx: ITransactionClient) => Promise<T>
): Promise<T>
```
#### Examples
```typescript
import {
withTransaction,
insert,
update,
} from '@starbemtech/star-db-query-builder'
// Create user with profile in a single transaction
const createUserWithProfile = async (userData: any, profileData: any) => {
return withTransaction(dbClient, async (tx) => {
// Create user
const user = await insert({
tableName: 'users',
dbClient: tx,
data: userData,
})
// Create user profile
const profile = await insert({
tableName: 'user_profiles',
dbClient: tx,
data: {
...profileData,
user_id: user.id,
},
})
return { user, profile }
})
}
// E-commerce order processing
const processOrder = async (orderData: any, orderItems: any[]) => {
return withTransaction(dbClient, async (tx) => {
// Create order
const order = await insert({
tableName: 'orders',
dbClient: tx,
data: {
...orderData,
status: 'pending',
total: 0,
},
})
let totalAmount = 0
// Create order items and calculate total
for (const item of orderItems) {
await insert({
tableName: 'order_items',
dbClient: tx,
data: {
...item,
order_id: order.id,
},
})
totalAmount += item.price * item.quantity
// Update product stock
await update({
tableName: 'products',
dbClient: tx,
id: item.product_id,
data: {
stock: { operator: '-', value: item.quantity },
},
})
}
// Update order total
await update({
tableName: 'orders',
dbClient: tx,
id: order.id,
data: {
total: totalAmount,
status: 'confirmed',
},
})
return { order, totalAmount }
})
}
```
### beginTransaction
Creates a transaction client for manual transaction management.
```typescript
const transaction = await beginTransaction(dbClient: IDatabaseClient): Promise<ITransactionClient>
```
#### Examples
```typescript
import {
beginTransaction,
insert,
update,
} from '@starbemtech/star-db-query-builder'
// Manual transaction management
const complexOperation = async () => {
const transaction = await beginTransaction(dbClient)
try {
// First operation
const user = await insert({
tableName: 'users',
dbClient: transaction,
data: { name: 'John Doe', email: 'john@example.com' },
})
// Second operation
const profile = await insert({
tableName: 'user_profiles',
dbClient: transaction,
data: { user_id: user.id, bio: 'Hello world' },
})
// Third operation
await update({
tableName: 'users',
dbClient: transaction,
id: user.id,
data: { profile_created: true },
})
// Commit all changes
await transaction.commit()
return { user, profile }
} catch (error) {
// Rollback on any error
await transaction.rollback()
throw error
}
}
```
### ITransactionClient Interface
```typescript
interface ITransactionClient {
query: <T>(sql: string, params?: any[]) => Promise<T>
commit: () => Promise<void>
rollback: () => Promise<void>
}
```
## Types and Interfaces
### Conditions
Used for building WHERE clauses with type safety.
```typescript
type Conditions<T> = {
[P in keyof T]?: Condition<T[P]>
} & LogicalCondition<T>
type Condition<T> = OperatorCondition | LogicalCondition<T>
interface OperatorCondition {
operator:
| '='
| '!='
| '>'
| '<'
| '>='
| '<='
| 'LIKE'
| 'NOT LIKE'
| 'ILIKE'
| 'IN'
| 'NOT IN'
| 'BETWEEN'
| 'IS NULL'
| 'IS NOT NULL'
| 'NOT EXISTS'
value: SimpleValue | SimpleValue[]
}
interface LogicalCondition<T> {
OR?: Conditions<T>[]
AND?: Conditions<T>[]
JOINS?: Conditions<object>
notExists?: OperatorCondition
}
```
### OrderBy
Used for specifying sort order.
```typescript
type OrderBy = { field: string; direction: 'ASC' | 'DESC' }[]
```
### JoinClause
Used for JOIN operations.
```typescript
interface JoinClause {
type: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'
table: string
on: string
}
```
## Advanced Usage
### Complex WHERE Conditions
```typescript
const users = await findMany({
tableName: 'users',
dbClient,
where: {
AND: [
{ status: { operator: '=', value: 'active' } },
{
OR: [
{ age: { operator: '>=', value: 18 } },
{ verified: { operator: '=', value: true } },
],
},
{ created_at: { operator: '>=', value: new Date('2023-01-01') } },
],
},
})
```
### Using Unaccent for PostgreSQL
```typescript
const users = await findMany({
tableName: 'users',
dbClient,
where: {
name: { operator: 'ILIKE', value: '%joão%' },
},
unaccent: true, // Enables unaccent search
})
```
### Using Unaccent for PostgreSQL
```typescript
const users = await findMany({
tableName: 'users',
dbClient,
where: {
name: { operator: 'ILIKE', value: '%joão%' },
},
unaccent: true, // Enables unaccent search
})
```
## Monitoring
The library provides a comprehensive monitoring system to track database operations and performance.
### Monitor Events
```typescript
import { monitor, MonitorEvents } from '@starbemtech/star-db-query-builder'
// Monitor connection events
monitor.on(MonitorEvents.CONNECTION_CREATED, (data) => {
console.log('Database connection created:', data)
})
// Monitor query events
monitor.on(MonitorEvents.QUERY_START, (data) => {
console.log('Query started:', {
sql: data.sql,
params: data.params,
clientType: data.clientType,
attempt: data.attempt,
})
})
monitor.on(MonitorEvents.QUERY_END, (data) => {
console.log('Query completed:', {
elapsedTime: data.elapsedTime,
clientType: data.clientType,
})
})
monitor.on(MonitorEvents.QUERY_ERROR, (data) => {
console.error('Query failed:', {
error: data.error,
sql: data.sql,
elapsedTime: data.elapsedTime,
})
})
// Monitor transaction events
monitor.on(MonitorEvents.TRANSACTION_COMMIT, (data) => {
console.log('Transaction committed:', data)
})
monitor.on(MonitorEvents.TRANSACTION_ROLLBACK, (data) => {
console.log('Transaction rolled back:', data)
})
// Monitor retry attempts
monitor.on(MonitorEvents.RETRY_ATTEMPT, (data) => {
console.warn('Retry attempt:', {
attempt: data.attempt,
error: data.error,
sql: data.sql,
})
})
```
### Custom Monitoring Implementation
```typescript
// Example: Log all database operations to a file
import fs from 'fs'
import path from 'path'
const logFile = path.join(__dirname, 'database.log')
monitor.on(MonitorEvents.QUERY_START, (data) => {
const logEntry = {
timestamp: new Date().toISOString(),
event: 'QUERY_START',
sql: data.sql,
params: data.params,
clientType: data.clientType,
}
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n')
})
monitor.on(MonitorEvents.QUERY_END, (data) => {
const logEntry = {
timestamp: new Date().toISOString(),
event: 'QUERY_END',
elapsedTime: data.elapsedTime,
clientType: data.clientType,
}
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n')
})
```
### Performance Monitoring
```typescript
// Track slow queries
monitor.on(MonitorEvents.QUERY_END, (data) => {
if (data.elapsedTime > 1000) {
// Queries taking more than 1 second
console.warn('Slow query detected:', {
sql: data.sql,
elapsedTime: data.elapsedTime,
clientType: data.clientType,
})
}
})
// Track connection pool usage
monitor.on(MonitorEvents.CONNECTION_CREATED, (data) => {
console.log('Connection pool status:', {
clientType: data.clientType,
poolOptions: data.poolOptions,
})
})
```
## Best Practices
### 1. Use TypeScript Types
```typescript
interface User {
id: string
name: string
email: string
created_at: Date
}
const users: User[] = await findMany<User>({
tableName: 'users',
dbClient,
where: { status: { operator: '=', value: 'active' } },
})
```
### 2. Use Specific Field Selection
```typescript
// Good: Select only needed fields
const users = await findMany({
tableName: 'users',
dbClient,
select: ['id', 'name', 'email'],
where: { status: { operator: '=', value: 'active' } },
})
// Avoid: Selecting all fields when not needed
const users = await findMany({
tableName: 'users',
dbClient,
where: { status: { operator: '=', value: 'active' } },
})
```
### 3. Use Pagination for Large Datasets
```typescript
const users = await findMany({
tableName: 'users',
dbClient,
limit: 50,
offset: 0,
orderBy: [{ field: 'created_at', direction: 'DESC' }],
})
```
### 4. Use Batch Operations When Possible
```typescript
// Good: Batch insert
const users = await insertMany({
tableName: 'users',
dbClient,
data: userArray,
})
// Avoid: Multiple individual inserts
for (const user of userArray) {
await insert({ tableName: 'users', dbClient, data: user })
}
```
### 5. Handle Errors Properly
```typescript
try {
const user = await findFirst({
tableName: 'users',
dbClient,
where: { email: { operator: '=', value: 'user@example.com' } },
})
} catch (error) {
console.error('Database error:', error.message)
// Handle error appropriately
}
```
### 6. Use Raw Queries Sparingly
```typescript
// Use built-in methods when possible
const users = await findMany({
tableName: 'users',
dbClient,
where: { status: { operator: '=', value: 'active' } },
})
// Use rawQuery only for complex operations
const complexStats = await rawQuery({
dbClient,
sql: 'SELECT ... complex aggregation ...',
})
```
### 7. Use Transactions for Data Consistency
```typescript
// Good: Use transactions for related operations
const createUserWithProfile = async (userData: any, profileData: any) => {
return withTransaction(dbClient, async (tx) => {
const user = await insert({
tableName: 'users',
dbClient: tx,
data: userData,
})
await insert({
tableName: 'user_profiles',
dbClient: tx,
data: { ...profileData, user_id: user.id },
})
return user
})
}
// Avoid: Multiple separate operations without transactions
const badUserCreation = async (userData: any, profileData: any) => {
const user = await insert({
tableName: 'users',
dbClient,
data: userData,
})
// If this fails, the user will be created but profile won't
await insert({
tableName: 'user_profiles',
dbClient,
data: { ...profileData, user_id: user.id },
})
return user
}
```
### 8. Keep Transactions Short
```typescript
// Good: Short, focused transaction
const updateUserStatus = async (userId: string, status: string) => {
return withTransaction(dbClient, async (tx) => {
await update({
tableName: 'users',
dbClient: tx,
id: userId,
data: { status },
})
await insert({
tableName: 'user_status_history',
dbClient: tx,
data: { user_id: userId, status, changed_at: new Date() },
})
})
}
// Avoid: Long-running transactions
const badTransaction = async () => {
return withTransaction(dbClient, async (tx) => {
// ... many operations
await someSlowOperation() // This could timeout
// ... more operations
})
}
```
## Error Handling
The library throws descriptive errors for common issues:
### Common Errors
- `Table name is required`
- `DB client is required`
- `Data object is required`
- `ID is required`
- `Where condition is required`
- `Raw query execution failed: [database message]`
- `Transaction execution failed: [database message]`
### Transaction Error Handling
```typescript
import {
withTransaction,
insert,
update,
} from '@starbemtech/star-db-query-builder'
const safeTransaction = async () => {
try {
return await withTransaction(dbClient, async (tx) => {
// Transaction operations
const result = await someOperation(tx)
return result
})
} catch (error) {
// Transaction was automatically rolled back
console.error('Transaction failed:', error.message)
// Handle specific error types
if (error.message.includes('deadlock detected')) {
// Handle deadlock - you might want to retry
console.warn('Deadlock detected, retrying...')
// Implement retry logic
} else if (error.message.includes('serialization failure')) {
// Handle serialization failure
console.warn('Serialization failure, retrying...')
// Implement retry logic
} else if (error.message.includes('connection lost')) {
// Handle connection issues
console.error('Database connection lost')
// Implement reconnection logic
} else {
// Handle other errors
console.error('Transaction error:', error.message)
}
throw error
}
}
```
### Retry Logic for Transient Errors
```typescript
const retryTransaction = async <T>(
transactionFn: (tx: ITransactionClient) => Promise<T>,
maxRetries: number = 3
): Promise<T> => {
let lastError: Error
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await withTransaction(dbClient, transactionFn)
} catch (error) {
lastError = error as Error
// Check if error is retryable
if (isRetryableError(error) && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000 // Exponential backoff
console.warn(
`Transaction attempt ${attempt} failed, retrying in ${delay}ms...`
)
await new Promise((resolve) => setTimeout(resolve, delay))
continue
}
throw error
}
}
throw lastError!
}
const isRetryableError = (error: any): boolean => {
const retryableErrors = [
'deadlock detected',
'serialization failure',
'connection lost',
'timeout',
]
return retryableErrors.some((msg) =>
error.message?.toLowerCase().includes(msg)
)
}
```
Always wrap database operations in try-catch blocks and handle errors appropriately in your application.
## Contributing
We welcome contributions to the Star DB Query Builder! Here's how you can help:
### Development Setup
1. **Clone the repository**
```bash
git clone https://github.com/starbem/star-db-query-builder.git
cd star-db-query-builder
```
2. **Install dependencies**
```bash
pnpm install
```
3. **Run tests**
```bash
pnpm test
```
4. **Run linting**
```bash
pnpm lint
```
5. **Build the project**
```bash
pnpm build
```
### Contributing Guidelines
- **Code Style**: Follow the existing code style and use Prettier for formatting
- **TypeScript**: Maintain strict TypeScript typing
- **Tests**: Add tests for new features and bug fixes
- **Documentation**: Update documentation for any API changes
- **Commit Messages**: Use conventional commit messages
### Pull Request Process
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Add tests for your changes
5. Ensure all tests pass (`pnpm test`)
6. Run linting (`pnpm lint`)
7. Commit your changes (`git commit -m 'feat: add amazing feature'`)
8. Push to your branch (`git push origin feature/amazing-feature`)
9. Open a Pull Request
### Reporting Issues
When reporting issues, please include:
- **Environment**: Node.js version, database type and version
- **Steps to Reproduce**: Clear steps to reproduce the issue
- **Expected Behavior**: What you expected to happen
- **Actual Behavior**: What actually happened
- **Code Sample**: Minimal code sample that demonstrates the issue
### Feature Requests
For feature requests, please:
- **Describe the feature**: Clear description of what you want
- **Use Case**: Explain why this feature would be useful
- **Proposed API**: If you have ideas for the API design
- **Alternatives**: Any alternative solutions you've considered
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Support
- **Documentation**: [GitHub Wiki](https://github.com/starbem/star-db-query-builder/wiki)
- **Issues**: [GitHub Issues](https://github.com/starbem/star-db-query-builder/issues)
- **Discussions**: [GitHub Discussions](https://github.com/starbem/star-db-query-builder/discussions)
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.
Made with ❤️ by the Starbem team