@starbemtech/star-db-query-builder
v1.2.1
Published
A query builder to be used with mysql or postgres
Readme
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
- Installation
- Quick Start
- Database Initialization
- Query Methods
- Transactions
- Types and Interfaces
- Advanced Usage
- Monitoring
- Best Practices
- Error Handling
- Contributing
- 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
npm install @starbemtech/star-db-query-builder
# or
pnpm add @starbemtech/star-db-query-builder
# or
yarn add @starbemtech/star-db-query-builderQuick Start
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: '[email protected]' } },
})
// Insert a new user
const newUser = await insert({
tableName: 'users',
dbClient,
data: { name: 'John Doe', email: '[email protected]' },
})Database Initialization
initDb
Initializes a database connection with the specified configuration.
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
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
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.
const dbClient = getDbClient(name?: string)// 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.
const result = await findFirst<T>({
tableName: string,
dbClient: IDatabaseClient,
select?: string[],
where?: Conditions<T>,
groupBy?: string[],
orderBy?: OrderBy
})Examples
// Find user by email
const user = await findFirst({
tableName: 'users',
dbClient,
where: {
email: { operator: '=', value: '[email protected]' },
},
})
// 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: '[email protected]' } },
{ 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.
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
// 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.
const result = await insert<P, R>({
tableName: string,
dbClient: IDatabaseClient,
data: P,
returning?: string[]
})Examples
// Simple insert
const user = await insert({
tableName: 'users',
dbClient,
data: {
name: 'John Doe',
email: '[email protected]',
age: 30,
},
})
// Insert with specific returning fields
const user = await insert({
tableName: 'users',
dbClient,
data: {
name: 'Jane Doe',
email: '[email protected]',
},
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: '[email protected]',
age: 30,
},
})insertMany
Inserts multiple records into the database in a single operation.
const results = await insertMany<P, R>({
tableName: string,
dbClient: IDatabaseClient,
data: P[],
returning?: string[]
})Examples
// Insert multiple users
const users = await insertMany({
tableName: 'users',
dbClient,
data: [
{ name: 'John Doe', email: '[email protected]' },
{ name: 'Jane Doe', email: '[email protected]' },
{ name: 'Bob Smith', email: '[email protected]' },
],
})
// Insert with returning fields
const users = await insertMany({
tableName: 'users',
dbClient,
data: [
{ name: 'John Doe', email: '[email protected]' },
{ name: 'Jane Doe', email: '[email protected]' },
],
returning: ['id', 'name', 'email'],
})update
Updates a single record by ID.
const result = await update<P, R>({
tableName: string,
dbClient: IDatabaseClient,
id: string,
data: P,
returning?: string[]
})Examples
// 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.
const results = await updateMany<P, R>({
tableName: string,
dbClient: IDatabaseClient,
data: P,
where: Conditions<T>,
returning?: string[]
})Examples
// 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).
await deleteOne<T>({
tableName: string,
dbClient: IDatabaseClient,
id: string,
permanently?: boolean
})Examples
// 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).
await deleteMany<T>({
tableName: string,
dbClient: IDatabaseClient,
ids: string[] | number[],
field?: string,
permanently?: boolean
})Examples
// 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.
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
// 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.
const result = await rawQuery<T>({
dbClient: IDatabaseClient,
sql: string,
params?: any[]
})Examples
// 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', '[email protected]'],
})
// 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.
const result = await withTransaction<T>(
dbClient: IDatabaseClient,
transactionFn: (tx: ITransactionClient) => Promise<T>
): Promise<T>Examples
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.
const transaction = await beginTransaction(dbClient: IDatabaseClient): Promise<ITransactionClient>Examples
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: '[email protected]' },
})
// 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
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.
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.
type OrderBy = { field: string; direction: 'ASC' | 'DESC' }[]JoinClause
Used for JOIN operations.
interface JoinClause {
type: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'
table: string
on: string
}Advanced Usage
Complex WHERE Conditions
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
const users = await findMany({
tableName: 'users',
dbClient,
where: {
name: { operator: 'ILIKE', value: '%joão%' },
},
unaccent: true, // Enables unaccent search
})Using Unaccent for PostgreSQL
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
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
// 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
// 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
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
// 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
const users = await findMany({
tableName: 'users',
dbClient,
limit: 50,
offset: 0,
orderBy: [{ field: 'created_at', direction: 'DESC' }],
})4. Use Batch Operations When Possible
// 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
try {
const user = await findFirst({
tableName: 'users',
dbClient,
where: { email: { operator: '=', value: '[email protected]' } },
})
} catch (error) {
console.error('Database error:', error.message)
// Handle error appropriately
}6. Use Raw Queries Sparingly
// 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
// 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
// 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 requiredDB client is requiredData object is requiredID is requiredWhere condition is requiredRaw query execution failed: [database message]Transaction execution failed: [database message]
Transaction Error Handling
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
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
Clone the repository
git clone https://github.com/starbem/star-db-query-builder.git cd star-db-query-builderInstall dependencies
pnpm installRun tests
pnpm testRun linting
pnpm lintBuild the project
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
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for your changes
- Ensure all tests pass (
pnpm test) - Run linting (
pnpm lint) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - 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 file for details.
Support
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Changelog
See CHANGELOG.md for a list of changes and version history.
Made with ❤️ by the Starbem team
