Skip to content

Commit 20f8adb

Browse files
authored
Develop (#2)
* chore: add comprehensive security-focused .gitignore - Added protection for secrets, credentials, and keys - Included patterns for environment files - Added coverage for IDE files, OS files, and build artifacts - Security-first approach to prevent accidental credential commits * chore: remove Azure DevOps pipeline - Removed azure-pipelines.yml (migrating to GitHub Actions) - All CI/CD now handled via GitHub Actions workflows * ci: add GitHub Actions CI/CD workflows - Added ci.yml for PR/push validation (lint, test, build) - Updated publish.yml with npm provenance support - Multi-node version testing (18, 20, 22) - Artifact upload for build outputs * docs: add AI assistant guidelines for DatabaseKit - Added comprehensive copilot-instructions.md - Includes architecture overview, naming conventions - Code patterns, anti-patterns, testing requirements - Security practices and release checklist * refactor: remove old core/ and nest/ directory structure - Deleted src/core/adapters/mongo.adapter.ts - Deleted src/core/adapters/postgres.adapter.ts - Deleted src/core/contracts.ts - Deleted src/core/database.ts - Deleted src/nest/database.decorators.ts - Deleted src/nest/database.module.ts BREAKING CHANGE: Complete restructure to new architecture * feat: add TypeScript contracts layer - Added database.contracts.ts with all interfaces and types - DatabaseConfig discriminated union (mongo | postgres) - Repository<T> interface for unified CRUD operations - PageResult and PageOptions for pagination - Module configuration types * feat: add database adapters layer - Added mongo.adapter.ts with Mongoose integration - Added postgres.adapter.ts with Knex integration - Both implement Repository<T> interface - Connection pooling and lifecycle management - Pagination support built-in * feat: add services layer - Added database.service.ts as main facade service - Added logger.service.ts for consistent logging - DatabaseService manages adapters and repositories - Implements NestJS OnModuleDestroy lifecycle hook * feat: add configuration layer - Added database.constants.ts with DI tokens - Added database.config.ts with config helpers - Environment-driven configuration support - DATABASE_TOKEN and DATABASE_OPTIONS_TOKEN for DI * feat: add middleware and filters - Added database.decorators.ts with @InjectDatabase() - Added database-exception.filter.ts for error handling - Global exception filter for database errors - Custom DI decorator for clean injection * feat: add utility functions - Added pagination.utils.ts with pagination helpers - Added validation.utils.ts with validation helpers - Added comprehensive unit tests for utilities - isValidMongoId, isValidUuid, sanitizeFilter, etc. * feat: add DatabaseKitModule - NestJS DynamicModule with forRoot() and forRootAsync() - forFeature() for feature module registration - Auto-connect option with graceful shutdown - Proper provider configuration and exports * feat: update public API exports - Clean barrel exports in index.ts - Exports only public API surface - Internal implementations kept private - Comprehensive type exports for consumers * chore: update package configuration - Upgraded to latest dependencies (ESLint 9, TS 5.8) - Updated tsconfig.json with path aliases - Added proper scripts for build, test, lint - Updated package.json metadata and peer deps * chore: add tooling configuration - Added eslint.config.mjs (ESLint 9 flat config) - Added jest.config.js for testing - Added .env.example with sample environment vars * docs: update project documentation - Updated README.md with new architecture - Updated CHANGELOG.md with v1.0.0 changes - Updated SECURITY.md with security policy - Updated CONTRIBUTING.md with contribution guide - Added TROUBLESHOOTING.md for common issues * feat: add transaction support for MongoDB and PostgreSQL - Add TransactionOptions, TransactionContext, MongoTransactionContext, PostgresTransactionContext types to contracts - Implement withTransaction() in MongoAdapter using Mongoose sessions - Implement withTransaction() in PostgresAdapter using Knex transactions - Support transaction isolation levels for PostgreSQL - Add retry logic with exponential backoff for transient failures - Expose withTransaction(), withMongoTransaction(), withPostgresTransaction() methods in DatabaseService - Add comprehensive tests for transaction functionality - Export all transaction types from public API Tests: 74 passed * feat: add bulk operations (insertMany, updateMany, deleteMany) - Add insertMany, updateMany, deleteMany methods to Repository interface - Implement bulk operations in MongoAdapter with session/transaction support - Implement bulk operations in PostgresAdapter with Knex support - Add comprehensive tests for all bulk operations - Both adapters support empty array handling for insertMany Repository interface now supports: - insertMany(data[]) -> T[] - Create multiple entities - updateMany(filter, update) -> number - Update matching entities - deleteMany(filter) -> number - Delete matching entities Tests: 82 passed * feat: add health check support for production monitoring - Add HealthCheckResult interface with healthy, responseTimeMs, type, error, details - Implement healthCheck() in MongoAdapter using admin.ping() command - Implement healthCheck() in PostgresAdapter using SELECT version() query - Expose healthCheck() method in DatabaseService facade - Include pool status and database version in health check details - Export HealthCheckResult type from public API - Add comprehensive tests for health check functionality Useful for: - Load balancer health endpoints - Kubernetes liveness/readiness probes - Application monitoring dashboards Tests: 92 passed * feat: add soft delete pattern support - Add softDelete and softDeleteField options to MongoRepositoryOptions - Add softDelete and softDeleteField options to PostgresEntityConfig - Implement soft delete in MongoAdapter: - All query methods filter out deleted records by default - deleteById/deleteMany set deletedAt instead of hard delete - softDelete(), softDeleteMany() for explicit soft delete - restore(), restoreMany() to recover soft-deleted records - findDeleted() and findAllWithDeleted() for querying deleted records - Implement soft delete in PostgresAdapter: - Same functionality with deleted_at as default field - Respects column whitelisting for security - Add 17 new tests for soft delete functionality - Total tests: 109 passing * feat: add automatic timestamp support (createdAt/updatedAt) - Add timestamps, createdAtField, updatedAtField to MongoRepositoryOptions - Add timestamps, createdAtField, updatedAtField to PostgresEntityConfig - Implement timestamp injection in MongoAdapter: - create() and insertMany() set createdAt automatically - updateById() and updateMany() set updatedAt automatically - Default field: 'createdAt', 'updatedAt' for MongoDB - Implement timestamp injection in PostgresAdapter: - Same behavior with 'created_at', 'updated_at' defaults - Respects custom field name configuration - Works seamlessly with soft delete feature - Add 11 new tests for timestamp functionality - Total tests: 120 passing * feat: add v1.0.0 production-ready features - Add findOne() method for single record lookup by filter - Add upsert() for update-or-insert operations - Add distinct() for unique field values - Add select() for field projection - Add PoolConfig interface for connection pool tuning - Add RepositoryHooks system (beforeCreate, afterCreate, beforeUpdate, afterUpdate, beforeDelete, afterDelete) - Implement hooks in both MongoDB and PostgreSQL adapters - Add comprehensive tests for all new features (133 total tests) - Update CHANGELOG for v1.0.0 release * docs: comprehensive README for v1.0.0 release * docs: add comprehensive Copilot instruction files - general.instructions.md: Main guidelines, architecture, patterns - adapters.instructions.md: Database adapter implementation guide - testing.instructions.md: Testing patterns and requirements - features.instructions.md: Feature implementation workflow - bugfix.instructions.md: Bug investigation and fix process All files updated to February 2026
1 parent 4a866ed commit 20f8adb

22 files changed

+5394
-206
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# Database Adapter Implementation Guide
2+
3+
Instructions for implementing or modifying database adapters in DatabaseKit.
4+
5+
---
6+
7+
## 🎯 Adapter Purpose
8+
9+
Adapters translate the unified `Repository<T>` interface to database-specific operations.
10+
Each adapter encapsulates ALL database-specific logic.
11+
12+
---
13+
14+
## 📐 Adapter Structure
15+
16+
Every adapter MUST implement these components:
17+
18+
```typescript
19+
class DatabaseAdapter {
20+
// 1. Connection Management
21+
connect(uri: string, options?: ConnectionOptions): Promise<void>;
22+
disconnect(): Promise<void>;
23+
isConnected(): boolean;
24+
25+
// 2. Repository Factory
26+
createRepository<T>(options: RepositoryOptions<T>): Repository<T>;
27+
28+
// 3. Transaction Support
29+
withTransaction<R>(fn: (ctx: TransactionContext) => Promise<R>): Promise<R>;
30+
31+
// 4. Health Check
32+
healthCheck(): Promise<HealthCheckResult>;
33+
}
34+
```
35+
36+
---
37+
38+
## ✅ Implementation Checklist
39+
40+
When creating or modifying an adapter:
41+
42+
### Connection
43+
44+
- [ ] Implement connection with retry logic
45+
- [ ] Handle connection pooling via `PoolConfig`
46+
- [ ] Implement graceful disconnect
47+
- [ ] Add connection state tracking
48+
49+
### Repository Methods (ALL required)
50+
51+
- [ ] `create(data)` - Insert single document
52+
- [ ] `findById(id)` - Find by primary key
53+
- [ ] `findOne(filter)` - Find first match
54+
- [ ] `findAll(filter)` - Find all matches
55+
- [ ] `findPage(options)` - Paginated query
56+
- [ ] `updateById(id, data)` - Update by primary key
57+
- [ ] `deleteById(id)` - Delete by primary key
58+
- [ ] `count(filter)` - Count matches
59+
- [ ] `exists(filter)` - Check existence
60+
- [ ] `insertMany(data[])` - Bulk insert
61+
- [ ] `updateMany(filter, data)` - Bulk update
62+
- [ ] `deleteMany(filter)` - Bulk delete
63+
- [ ] `upsert(filter, data)` - Update or insert
64+
- [ ] `distinct(field, filter)` - Unique values
65+
- [ ] `select(filter, fields)` - Field projection
66+
67+
### Optional Features
68+
69+
- [ ] `softDelete(id)` - When `softDelete: true`
70+
- [ ] `restore(id)` - When `softDelete: true`
71+
- [ ] `findWithDeleted(filter)` - When `softDelete: true`
72+
73+
### Cross-Cutting
74+
75+
- [ ] Timestamps (`createdAt`, `updatedAt`)
76+
- [ ] Hooks (`beforeCreate`, `afterCreate`, etc.)
77+
- [ ] Transaction context repositories
78+
- [ ] Health check with connection details
79+
80+
---
81+
82+
## 🔧 MongoDB Adapter Patterns
83+
84+
### Model Registration
85+
86+
```typescript
87+
// Use Mongoose schema
88+
const schema = new Schema<T>(definition, { timestamps: true });
89+
const model = mongoose.model<T>(name, schema);
90+
```
91+
92+
### Query Translation
93+
94+
```typescript
95+
// MongoDB operators are native
96+
{
97+
age: {
98+
$gte: 18;
99+
}
100+
} // Direct pass-through
101+
{
102+
status: {
103+
$in: ["active", "pending"];
104+
}
105+
}
106+
```
107+
108+
### Transactions
109+
110+
```typescript
111+
// Use ClientSession
112+
const session = await mongoose.startSession();
113+
session.startTransaction();
114+
try {
115+
await model.create([data], { session });
116+
await session.commitTransaction();
117+
} catch (e) {
118+
await session.abortTransaction();
119+
throw e;
120+
} finally {
121+
session.endSession();
122+
}
123+
```
124+
125+
### ID Handling
126+
127+
```typescript
128+
// MongoDB uses ObjectId
129+
import { Types } from "mongoose";
130+
const objectId = new Types.ObjectId(id);
131+
```
132+
133+
---
134+
135+
## 🔧 PostgreSQL Adapter Patterns
136+
137+
### Table Configuration
138+
139+
```typescript
140+
// Use Knex table name
141+
const table = knex<T>(tableName);
142+
```
143+
144+
### Query Translation
145+
146+
```typescript
147+
// Convert operators to SQL
148+
{ price: { gt: 100 } } → .where('price', '>', 100)
149+
{ status: { in: [...] } } → .whereIn('status', [...])
150+
{ name: { like: '%john%' } } → .whereILike('name', '%john%')
151+
{ deleted: { isNull: true }} → .whereNull('deleted')
152+
```
153+
154+
### Transactions
155+
156+
```typescript
157+
// Use Knex transaction
158+
await knex.transaction(async (trx) => {
159+
await trx("users").insert(data);
160+
await trx("orders").insert(orderData);
161+
});
162+
```
163+
164+
### ID Handling
165+
166+
```typescript
167+
// PostgreSQL uses auto-increment or UUID
168+
// Return inserted row to get ID
169+
const [inserted] = await knex("users").insert(data).returning("*");
170+
```
171+
172+
---
173+
174+
## 🪝 Hook Implementation
175+
176+
All adapters must support lifecycle hooks:
177+
178+
```typescript
179+
interface RepositoryHooks<T> {
180+
beforeCreate?(ctx: HookContext<T>): Partial<T> | Promise<Partial<T>>;
181+
afterCreate?(entity: T): void | Promise<void>;
182+
beforeUpdate?(ctx: HookContext<T>): Partial<T> | Promise<Partial<T>>;
183+
afterUpdate?(entity: T | null): void | Promise<void>;
184+
beforeDelete?(id: string | number): void | Promise<void>;
185+
afterDelete?(success: boolean): void | Promise<void>;
186+
}
187+
```
188+
189+
### Hook Execution Order
190+
191+
1. `beforeCreate` → modify data → `create()``afterCreate`
192+
2. `beforeUpdate` → modify data → `updateById()``afterUpdate`
193+
3. `beforeDelete``deleteById()``afterDelete`
194+
195+
### Hook Context
196+
197+
```typescript
198+
interface HookContext<T> {
199+
data: Partial<T>; // The data being created/updated
200+
repository: Repository<T>; // Self-reference for lookups
201+
}
202+
```
203+
204+
---
205+
206+
## ⏱️ Timestamp Implementation
207+
208+
When `timestamps: true`:
209+
210+
```typescript
211+
// On create
212+
data.createdAt = new Date();
213+
data.updatedAt = new Date();
214+
215+
// On update
216+
data.updatedAt = new Date();
217+
// Never modify createdAt on update!
218+
```
219+
220+
---
221+
222+
## 🗑️ Soft Delete Implementation
223+
224+
When `softDelete: true`:
225+
226+
```typescript
227+
// softDelete() - Set deletedAt
228+
await repo.updateById(id, { deletedAt: new Date() });
229+
230+
// restore() - Clear deletedAt
231+
await repo.updateById(id, { deletedAt: null });
232+
233+
// findAll/findOne - Exclude deleted
234+
filter.deletedAt = { isNull: true }; // or { $eq: null } for Mongo
235+
236+
// findWithDeleted - Include deleted
237+
// Skip the deletedAt filter
238+
```
239+
240+
---
241+
242+
## 🧪 Testing Requirements
243+
244+
Every adapter must have tests for:
245+
246+
1. **Connection** - Connect, disconnect, reconnect
247+
2. **CRUD** - All basic operations
248+
3. **Bulk** - insertMany, updateMany, deleteMany
249+
4. **Queries** - Filters, pagination, sorting
250+
5. **Transactions** - Commit, rollback, nested
251+
6. **Hooks** - All 6 hooks fire correctly
252+
7. **Timestamps** - createdAt/updatedAt auto-set
253+
8. **Soft Delete** - delete, restore, findWithDeleted
254+
9. **Health Check** - Returns correct status
255+
256+
---
257+
258+
## 📝 Adding a New Adapter
259+
260+
To add a new database (e.g., SQLite, MySQL):
261+
262+
1. Create `src/adapters/sqlite.adapter.ts`
263+
2. Implement `DatabaseAdapter` interface
264+
3. Add to `DatabaseService`:
265+
```typescript
266+
createSqliteRepository<T>(opts): Repository<T>
267+
```
268+
4. Add connection config type to `DatabaseConfig`
269+
5. Add comprehensive tests
270+
6. Export from `index.ts` if public
271+
7. Document in README
272+
273+
---
274+
275+
_Last updated: February 2026_

0 commit comments

Comments
 (0)