@ciscode/database-kit 1.0.0 β†’ 1.0.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 (48) hide show
  1. package/CHANGELOG.md +50 -4
  2. package/README.md +487 -148
  3. package/dist/adapters/mongo.adapter.d.ts +53 -3
  4. package/dist/adapters/mongo.adapter.d.ts.map +1 -1
  5. package/dist/adapters/mongo.adapter.js +410 -27
  6. package/dist/adapters/mongo.adapter.js.map +1 -1
  7. package/dist/adapters/postgres.adapter.d.ts +50 -3
  8. package/dist/adapters/postgres.adapter.d.ts.map +1 -1
  9. package/dist/adapters/postgres.adapter.js +439 -45
  10. package/dist/adapters/postgres.adapter.js.map +1 -1
  11. package/dist/config/database.config.d.ts +1 -1
  12. package/dist/config/database.config.d.ts.map +1 -1
  13. package/dist/config/database.config.js +13 -13
  14. package/dist/config/database.config.js.map +1 -1
  15. package/dist/config/database.constants.js +7 -7
  16. package/dist/contracts/database.contracts.d.ts +283 -6
  17. package/dist/contracts/database.contracts.d.ts.map +1 -1
  18. package/dist/contracts/database.contracts.js +6 -1
  19. package/dist/contracts/database.contracts.js.map +1 -1
  20. package/dist/database-kit.module.d.ts +2 -2
  21. package/dist/database-kit.module.d.ts.map +1 -1
  22. package/dist/database-kit.module.js +1 -2
  23. package/dist/database-kit.module.js.map +1 -1
  24. package/dist/filters/database-exception.filter.d.ts +1 -1
  25. package/dist/filters/database-exception.filter.d.ts.map +1 -1
  26. package/dist/filters/database-exception.filter.js +43 -43
  27. package/dist/filters/database-exception.filter.js.map +1 -1
  28. package/dist/index.d.ts +10 -10
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/middleware/database.decorators.d.ts.map +1 -1
  32. package/dist/middleware/database.decorators.js.map +1 -1
  33. package/dist/services/database.service.d.ts +83 -5
  34. package/dist/services/database.service.d.ts.map +1 -1
  35. package/dist/services/database.service.js +136 -16
  36. package/dist/services/database.service.js.map +1 -1
  37. package/dist/services/logger.service.d.ts +1 -1
  38. package/dist/services/logger.service.d.ts.map +1 -1
  39. package/dist/services/logger.service.js +1 -1
  40. package/dist/services/logger.service.js.map +1 -1
  41. package/dist/utils/pagination.utils.d.ts +2 -2
  42. package/dist/utils/pagination.utils.d.ts.map +1 -1
  43. package/dist/utils/pagination.utils.js +9 -6
  44. package/dist/utils/pagination.utils.js.map +1 -1
  45. package/dist/utils/validation.utils.d.ts.map +1 -1
  46. package/dist/utils/validation.utils.js +5 -5
  47. package/dist/utils/validation.utils.js.map +1 -1
  48. package/package.json +28 -8
package/README.md CHANGED
@@ -5,18 +5,82 @@ A NestJS-friendly, OOP-style database library providing a unified repository API
5
5
  [![npm version](https://img.shields.io/npm/v/@ciscode/database-kit.svg)](https://www.npmjs.com/package/@ciscode/database-kit)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Node.js Version](https://img.shields.io/node/v/@ciscode/database-kit.svg)](https://nodejs.org)
8
+ [![Tests](https://img.shields.io/badge/tests-133%20passed-brightgreen.svg)]()
9
+
10
+ ---
11
+
12
+ ## 🎯 How It Works
13
+
14
+ **DatabaseKit** provides a unified abstraction layer over MongoDB and PostgreSQL, allowing you to write database operations once and run them on either database system. Here's how the architecture works:
15
+
16
+ ```
17
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
18
+ β”‚ Your NestJS Application β”‚
19
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
20
+ β”‚ β”‚
21
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” inject β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
22
+ β”‚ β”‚ Service β”‚ ──────────── β”‚ DatabaseService β”‚ β”‚
23
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”œβ”€β”€ createMongoRepository() β”‚ β”‚
24
+ β”‚ β”‚ └── createPostgresRepository() β”‚ β”‚
25
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
26
+ β”‚ β”‚ β”‚
27
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
28
+ β”‚ β”‚ β”‚ β”‚
29
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”β”‚
30
+ β”‚ β”‚ MongoAdapter β”‚ β”‚ PostgresAdapterβ”‚
31
+ β”‚ β”‚ (Mongoose) β”‚ β”‚ (Knex.js) β”‚β”‚
32
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
33
+ β”‚ β”‚ β”‚ β”‚
34
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”˜
35
+ β”‚ β”‚
36
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
37
+ β”‚ MongoDB β”‚ β”‚ PostgreSQL β”‚
38
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
39
+ ```
40
+
41
+ ### The Repository Pattern
42
+
43
+ Every repository (MongoDB or PostgreSQL) implements the **same interface**:
44
+
45
+ ```typescript
46
+ const user = await repo.create({ name: "John" }); // Works on both!
47
+ const found = await repo.findById("123"); // Works on both!
48
+ const page = await repo.findPage({ page: 1 }); // Works on both!
49
+ ```
50
+
51
+ This means you can:
52
+
53
+ - **Switch databases** without changing your service code
54
+ - **Test with MongoDB** and deploy with PostgreSQL (or vice versa)
55
+ - **Use the same mental model** regardless of database
8
56
 
9
57
  ---
10
58
 
11
59
  ## ✨ Features
12
60
 
61
+ ### Core Features
62
+
13
63
  - βœ… **Unified Repository API** - Same interface for MongoDB and PostgreSQL
14
64
  - βœ… **NestJS Integration** - First-class support with `DatabaseKitModule`
15
65
  - βœ… **TypeScript First** - Full type safety and IntelliSense
16
66
  - βœ… **Pagination Built-in** - Consistent pagination across databases
17
- - βœ… **Environment-Driven** - Zero hardcoding, all config from env vars
18
- - βœ… **Exception Handling** - Global exception filter for database errors
19
- - βœ… **Clean Architecture** - Follows CISCODE standards and best practices
67
+
68
+ ### Advanced Features
69
+
70
+ - βœ… **Transactions** - ACID transactions with automatic retry logic
71
+ - βœ… **Bulk Operations** - `insertMany`, `updateMany`, `deleteMany`
72
+ - βœ… **Soft Delete** - Non-destructive deletion with restore capability
73
+ - βœ… **Timestamps** - Automatic `createdAt`/`updatedAt` tracking
74
+ - βœ… **Health Checks** - Database monitoring and connection status
75
+ - βœ… **Connection Pool Config** - Fine-tune pool settings for performance
76
+ - βœ… **Event Hooks** - Lifecycle callbacks (beforeCreate, afterUpdate, etc.)
77
+
78
+ ### Query Features
79
+
80
+ - βœ… **findOne** - Find single record by filter
81
+ - βœ… **upsert** - Update or insert in one operation
82
+ - βœ… **distinct** - Get unique values for a field
83
+ - βœ… **select** - Field projection (return only specific fields)
20
84
 
21
85
  ---
22
86
 
@@ -28,22 +92,18 @@ npm install @ciscode/database-kit
28
92
 
29
93
  ### Peer Dependencies
30
94
 
31
- Make sure you have NestJS installed:
32
-
33
95
  ```bash
34
96
  npm install @nestjs/common @nestjs/core reflect-metadata
35
97
  ```
36
98
 
37
99
  ### Database Drivers
38
100
 
39
- Install the driver for your database:
40
-
41
101
  ```bash
42
102
  # For MongoDB
43
103
  npm install mongoose
44
104
 
45
105
  # For PostgreSQL
46
- npm install pg
106
+ npm install pg knex
47
107
  ```
48
108
 
49
109
  ---
@@ -70,7 +130,7 @@ import { DatabaseKitModule } from "@ciscode/database-kit";
70
130
  export class AppModule {}
71
131
  ```
72
132
 
73
- ### 2. Inject and Use
133
+ ### 2. Create a Repository and Use It
74
134
 
75
135
  ```typescript
76
136
  // users.service.ts
@@ -86,6 +146,7 @@ interface User {
86
146
  _id: string;
87
147
  name: string;
88
148
  email: string;
149
+ createdAt: Date;
89
150
  }
90
151
 
91
152
  @Injectable()
@@ -93,172 +154,360 @@ export class UsersService {
93
154
  private readonly usersRepo: Repository<User>;
94
155
 
95
156
  constructor(@InjectDatabase() private readonly db: DatabaseService) {
96
- this.usersRepo = db.createMongoRepository<User>({ model: UserModel });
157
+ // For MongoDB
158
+ this.usersRepo = db.createMongoRepository<User>({
159
+ model: UserModel,
160
+ timestamps: true, // Auto createdAt/updatedAt
161
+ softDelete: true, // Enable soft delete
162
+ hooks: {
163
+ // Lifecycle hooks
164
+ beforeCreate: (ctx) => {
165
+ console.log("Creating user:", ctx.data);
166
+ return ctx.data; // Can modify data
167
+ },
168
+ afterCreate: (user) => {
169
+ console.log("User created:", user._id);
170
+ },
171
+ },
172
+ });
97
173
  }
98
174
 
175
+ // CREATE
99
176
  async createUser(data: Partial<User>): Promise<User> {
100
177
  return this.usersRepo.create(data);
101
178
  }
102
179
 
180
+ // READ
103
181
  async getUser(id: string): Promise<User | null> {
104
182
  return this.usersRepo.findById(id);
105
183
  }
106
184
 
185
+ async getUserByEmail(email: string): Promise<User | null> {
186
+ return this.usersRepo.findOne({ email });
187
+ }
188
+
107
189
  async listUsers(page = 1, limit = 10) {
108
- return this.usersRepo.findPage({ page, limit });
190
+ return this.usersRepo.findPage({
191
+ page,
192
+ limit,
193
+ sort: "-createdAt",
194
+ });
195
+ }
196
+
197
+ // UPDATE
198
+ async updateUser(id: string, data: Partial<User>): Promise<User | null> {
199
+ return this.usersRepo.updateById(id, data);
200
+ }
201
+
202
+ // UPSERT (update or create)
203
+ async upsertByEmail(email: string, data: Partial<User>): Promise<User> {
204
+ return this.usersRepo.upsert({ email }, data);
205
+ }
206
+
207
+ // DELETE (soft delete if enabled)
208
+ async deleteUser(id: string): Promise<boolean> {
209
+ return this.usersRepo.deleteById(id);
210
+ }
211
+
212
+ // RESTORE (only with soft delete)
213
+ async restoreUser(id: string): Promise<User | null> {
214
+ return this.usersRepo.restore!(id);
215
+ }
216
+
217
+ // BULK OPERATIONS
218
+ async createManyUsers(users: Partial<User>[]): Promise<User[]> {
219
+ return this.usersRepo.insertMany(users);
220
+ }
221
+
222
+ // DISTINCT VALUES
223
+ async getUniqueEmails(): Promise<string[]> {
224
+ return this.usersRepo.distinct("email");
225
+ }
226
+
227
+ // SELECT SPECIFIC FIELDS
228
+ async getUserNames(): Promise<Pick<User, "name" | "email">[]> {
229
+ return this.usersRepo.select({}, ["name", "email"]);
109
230
  }
110
231
  }
111
232
  ```
112
233
 
113
234
  ---
114
235
 
115
- ## βš™οΈ Configuration
236
+ ## πŸ“– Complete Repository API
116
237
 
117
- ### Environment Variables
238
+ ```typescript
239
+ interface Repository<T> {
240
+ // ─────────────────────────────────────────────────────────────
241
+ // CRUD Operations
242
+ // ─────────────────────────────────────────────────────────────
243
+ create(data: Partial<T>): Promise<T>;
244
+ findById(id: string | number): Promise<T | null>;
245
+ findOne(filter: Filter): Promise<T | null>;
246
+ findAll(filter?: Filter): Promise<T[]>;
247
+ findPage(options?: PageOptions): Promise<PageResult<T>>;
248
+ updateById(id: string | number, update: Partial<T>): Promise<T | null>;
249
+ deleteById(id: string | number): Promise<boolean>;
250
+ count(filter?: Filter): Promise<number>;
251
+ exists(filter?: Filter): Promise<boolean>;
252
+
253
+ // ─────────────────────────────────────────────────────────────
254
+ // Bulk Operations
255
+ // ─────────────────────────────────────────────────────────────
256
+ insertMany(data: Partial<T>[]): Promise<T[]>;
257
+ updateMany(filter: Filter, update: Partial<T>): Promise<number>;
258
+ deleteMany(filter: Filter): Promise<number>;
259
+
260
+ // ─────────────────────────────────────────────────────────────
261
+ // Advanced Queries
262
+ // ─────────────────────────────────────────────────────────────
263
+ upsert(filter: Filter, data: Partial<T>): Promise<T>;
264
+ distinct<K extends keyof T>(field: K, filter?: Filter): Promise<T[K][]>;
265
+ select<K extends keyof T>(filter: Filter, fields: K[]): Promise<Pick<T, K>[]>;
266
+
267
+ // ─────────────────────────────────────────────────────────────
268
+ // Soft Delete (when enabled)
269
+ // ─────────────────────────────────────────────────────────────
270
+ softDelete?(id: string | number): Promise<boolean>;
271
+ softDeleteMany?(filter: Filter): Promise<number>;
272
+ restore?(id: string | number): Promise<T | null>;
273
+ restoreMany?(filter: Filter): Promise<number>;
274
+ findWithDeleted?(filter?: Filter): Promise<T[]>;
275
+ }
276
+ ```
277
+
278
+ ---
118
279
 
119
- | Variable | Description | Required |
120
- | ----------------------------- | ---------------------------------------- | -------------- |
121
- | `DATABASE_TYPE` | Database type (`mongo` or `postgres`) | Yes |
122
- | `MONGO_URI` | MongoDB connection string | For MongoDB |
123
- | `DATABASE_URL` | PostgreSQL connection string | For PostgreSQL |
124
- | `DATABASE_POOL_SIZE` | Connection pool size (default: 10) | No |
125
- | `DATABASE_CONNECTION_TIMEOUT` | Connection timeout in ms (default: 5000) | No |
280
+ ## ⚑ Advanced Features
126
281
 
127
- ### Synchronous Configuration
282
+ ### Transactions
283
+
284
+ Execute multiple operations atomically:
128
285
 
129
286
  ```typescript
130
- DatabaseKitModule.forRoot({
131
- config: {
132
- type: "postgres",
133
- connectionString: "postgresql://user:pass@localhost:5432/mydb",
287
+ // MongoDB Transaction
288
+ const result = await db.getMongoAdapter().withTransaction(
289
+ async (ctx) => {
290
+ const userRepo = ctx.createRepository<User>({ model: UserModel });
291
+ const orderRepo = ctx.createRepository<Order>({ model: OrderModel });
292
+
293
+ const user = await userRepo.create({ name: "John" });
294
+ const order = await orderRepo.create({ userId: user._id, total: 99.99 });
295
+
296
+ return { user, order };
134
297
  },
135
- autoConnect: true, // default: true
136
- });
298
+ {
299
+ maxRetries: 3, // Retry on transient errors
300
+ retryDelayMs: 100,
301
+ },
302
+ );
303
+
304
+ // PostgreSQL Transaction
305
+ const result = await db.getPostgresAdapter().withTransaction(
306
+ async (ctx) => {
307
+ const userRepo = ctx.createRepository<User>({ table: "users" });
308
+ const orderRepo = ctx.createRepository<Order>({ table: "orders" });
309
+
310
+ const user = await userRepo.create({ name: "John" });
311
+ const order = await orderRepo.create({ user_id: user.id, total: 99.99 });
312
+
313
+ return { user, order };
314
+ },
315
+ {
316
+ isolationLevel: "serializable",
317
+ },
318
+ );
137
319
  ```
138
320
 
139
- ### Async Configuration (Recommended)
321
+ ### Event Hooks
322
+
323
+ React to repository lifecycle events:
140
324
 
141
325
  ```typescript
142
- import { ConfigModule, ConfigService } from "@nestjs/config";
326
+ const repo = db.createMongoRepository<User>({
327
+ model: UserModel,
328
+ hooks: {
329
+ // Before create - can modify data
330
+ beforeCreate: (context) => {
331
+ console.log("Creating:", context.data);
332
+ return {
333
+ ...context.data,
334
+ normalizedEmail: context.data.email?.toLowerCase(),
335
+ };
336
+ },
143
337
 
144
- DatabaseKitModule.forRootAsync({
145
- imports: [ConfigModule],
146
- useFactory: (config: ConfigService) => ({
147
- config: {
148
- type: config.get("DATABASE_TYPE") as "mongo" | "postgres",
149
- connectionString: config.get("DATABASE_URL")!,
338
+ // After create - for side effects
339
+ afterCreate: (user) => {
340
+ sendWelcomeEmail(user.email);
150
341
  },
151
- }),
152
- inject: [ConfigService],
342
+
343
+ // Before update - can modify data
344
+ beforeUpdate: (context) => {
345
+ return { ...context.data, updatedBy: "system" };
346
+ },
347
+
348
+ // After update
349
+ afterUpdate: (user) => {
350
+ if (user) invalidateCache(user._id);
351
+ },
352
+
353
+ // Before delete - for validation
354
+ beforeDelete: (id) => {
355
+ console.log("Deleting user:", id);
356
+ },
357
+
358
+ // After delete
359
+ afterDelete: (success) => {
360
+ if (success) console.log("User deleted");
361
+ },
362
+ },
153
363
  });
154
364
  ```
155
365
 
156
- ### Multiple Databases
366
+ ### Connection Pool Configuration
367
+
368
+ Fine-tune database connection pooling:
157
369
 
158
370
  ```typescript
159
- @Module({
160
- imports: [
161
- // Primary database
162
- DatabaseKitModule.forRoot({
163
- config: { type: "mongo", connectionString: process.env.MONGO_URI! },
164
- }),
165
- // Analytics database
166
- DatabaseKitModule.forFeature("ANALYTICS_DB", {
167
- type: "postgres",
168
- connectionString: process.env.ANALYTICS_DB_URL!,
169
- }),
170
- ],
171
- })
172
- export class AppModule {}
371
+ // MongoDB
372
+ DatabaseKitModule.forRoot({
373
+ config: {
374
+ type: "mongo",
375
+ connectionString: process.env.MONGO_URI!,
376
+ pool: {
377
+ min: 5,
378
+ max: 50,
379
+ idleTimeoutMs: 30000,
380
+ acquireTimeoutMs: 60000,
381
+ },
382
+ // MongoDB-specific
383
+ serverSelectionTimeoutMS: 5000,
384
+ socketTimeoutMS: 45000,
385
+ },
386
+ });
173
387
 
174
- // Usage
175
- @Injectable()
176
- export class AnalyticsService {
177
- constructor(
178
- @InjectDatabaseByToken("ANALYTICS_DB")
179
- private readonly analyticsDb: DatabaseService,
180
- ) {}
181
- }
388
+ // PostgreSQL
389
+ DatabaseKitModule.forRoot({
390
+ config: {
391
+ type: "postgres",
392
+ connectionString: process.env.DATABASE_URL!,
393
+ pool: {
394
+ min: 2,
395
+ max: 20,
396
+ idleTimeoutMs: 30000,
397
+ acquireTimeoutMs: 60000,
398
+ },
399
+ },
400
+ });
182
401
  ```
183
402
 
184
- ---
185
-
186
- ## πŸ“– Repository API
403
+ ### Health Checks
187
404
 
188
- Both MongoDB and PostgreSQL repositories expose the same interface:
405
+ Monitor database health in production:
189
406
 
190
407
  ```typescript
191
- interface Repository<T> {
192
- create(data: Partial<T>): Promise<T>;
193
- findById(id: string | number): Promise<T | null>;
194
- findAll(filter?: Filter): Promise<T[]>;
195
- findPage(options?: PageOptions): Promise<PageResult<T>>;
196
- updateById(id: string | number, update: Partial<T>): Promise<T | null>;
197
- deleteById(id: string | number): Promise<boolean>;
198
- count(filter?: Filter): Promise<number>;
199
- exists(filter?: Filter): Promise<boolean>;
408
+ @Controller("health")
409
+ export class HealthController {
410
+ constructor(@InjectDatabase() private readonly db: DatabaseService) {}
411
+
412
+ @Get()
413
+ async check() {
414
+ const mongoHealth = await this.db.getMongoAdapter().healthCheck();
415
+ // Returns:
416
+ // {
417
+ // healthy: true,
418
+ // responseTimeMs: 12,
419
+ // type: 'mongo',
420
+ // details: {
421
+ // version: 'MongoDB 6.0',
422
+ // activeConnections: 5,
423
+ // poolSize: 10,
424
+ // }
425
+ // }
426
+
427
+ return {
428
+ status: mongoHealth.healthy ? "healthy" : "unhealthy",
429
+ database: mongoHealth,
430
+ };
431
+ }
200
432
  }
201
433
  ```
202
434
 
203
- ### Pagination
435
+ ### Soft Delete
436
+
437
+ Non-destructive deletion with restore capability:
204
438
 
205
439
  ```typescript
206
- const result = await repo.findPage({
207
- filter: { status: "active" },
208
- page: 1,
209
- limit: 20,
210
- sort: "-createdAt", // or { createdAt: -1 }
440
+ const repo = db.createMongoRepository<User>({
441
+ model: UserModel,
442
+ softDelete: true, // Enable soft delete
443
+ softDeleteField: "deletedAt", // Default field name
211
444
  });
212
445
 
213
- // Result:
214
- // {
215
- // data: [...],
216
- // page: 1,
217
- // limit: 20,
218
- // total: 150,
219
- // pages: 8
220
- // }
446
+ // "Delete" - sets deletedAt timestamp
447
+ await repo.deleteById("123");
448
+
449
+ // Regular queries exclude deleted records
450
+ await repo.findAll(); // Only non-deleted users
451
+
452
+ // Include deleted records
453
+ await repo.findWithDeleted!(); // All users including deleted
454
+
455
+ // Restore a deleted record
456
+ await repo.restore!("123");
221
457
  ```
222
458
 
223
- ### MongoDB Repository
459
+ ### Timestamps
460
+
461
+ Automatic created/updated tracking:
224
462
 
225
463
  ```typescript
226
- import { Model } from "mongoose";
464
+ const repo = db.createMongoRepository<User>({
465
+ model: UserModel,
466
+ timestamps: true, // Enable timestamps
467
+ createdAtField: "createdAt", // Default
468
+ updatedAtField: "updatedAt", // Default
469
+ });
227
470
 
228
- // Create your Mongoose model as usual
229
- const usersRepo = db.createMongoRepository<User>({ model: UserModel });
471
+ // create() automatically sets createdAt
472
+ const user = await repo.create({ name: "John" });
473
+ // user.createdAt = 2026-02-01T12:00:00.000Z
474
+
475
+ // updateById() automatically sets updatedAt
476
+ await repo.updateById(user._id, { name: "Johnny" });
477
+ // user.updatedAt = 2026-02-01T12:01:00.000Z
230
478
  ```
231
479
 
232
- ### PostgreSQL Repository
480
+ ---
233
481
 
234
- ```typescript
235
- interface Order {
236
- id: string;
237
- user_id: string;
238
- total: number;
239
- created_at: Date;
240
- }
482
+ ## πŸ” Query Operators
483
+
484
+ ### MongoDB Queries
241
485
 
242
- const ordersRepo = db.createPostgresRepository<Order>({
243
- table: "orders",
244
- primaryKey: "id",
245
- columns: ["id", "user_id", "total", "created_at", "is_deleted"],
246
- defaultFilter: { is_deleted: false }, // Soft delete support
486
+ Standard MongoDB query syntax:
487
+
488
+ ```typescript
489
+ await repo.findAll({
490
+ age: { $gte: 18, $lt: 65 },
491
+ status: { $in: ["active", "pending"] },
492
+ name: { $regex: /john/i },
247
493
  });
248
494
  ```
249
495
 
250
- ### PostgreSQL Advanced Filters
496
+ ### PostgreSQL Queries
497
+
498
+ Structured query operators:
251
499
 
252
500
  ```typescript
253
- // Comparison operators
501
+ // Comparison
254
502
  await repo.findAll({
255
- price: { gt: 100, lte: 500 },
256
- status: { ne: "cancelled" },
503
+ price: { gt: 100, lte: 500 }, // > 100 AND <= 500
504
+ status: { ne: "cancelled" }, // != 'cancelled'
257
505
  });
258
506
 
259
507
  // IN / NOT IN
260
508
  await repo.findAll({
261
509
  category: { in: ["electronics", "books"] },
510
+ brand: { nin: ["unknown"] },
262
511
  });
263
512
 
264
513
  // LIKE (case-insensitive)
@@ -269,34 +518,91 @@ await repo.findAll({
269
518
  // NULL checks
270
519
  await repo.findAll({
271
520
  deleted_at: { isNull: true },
521
+ email: { isNotNull: true },
522
+ });
523
+
524
+ // Sorting
525
+ await repo.findPage({
526
+ sort: "-created_at,name", // DESC created_at, ASC name
527
+ // or: { created_at: -1, name: 1 }
272
528
  });
273
529
  ```
274
530
 
275
531
  ---
276
532
 
277
- ## πŸ›‘οΈ Error Handling
533
+ ## βš™οΈ Configuration
278
534
 
279
- ### Global Exception Filter
535
+ ### Environment Variables
536
+
537
+ | Variable | Description | Required |
538
+ | ------------------- | ---------------------------- | ------------------ |
539
+ | `DATABASE_TYPE` | `mongo` or `postgres` | Yes |
540
+ | `MONGO_URI` | MongoDB connection string | For MongoDB |
541
+ | `DATABASE_URL` | PostgreSQL connection string | For PostgreSQL |
542
+ | `DATABASE_POOL_MIN` | Min pool connections | No (default: 0) |
543
+ | `DATABASE_POOL_MAX` | Max pool connections | No (default: 10) |
544
+ | `DATABASE_TIMEOUT` | Connection timeout (ms) | No (default: 5000) |
280
545
 
281
- Register the filter globally to catch and format database errors:
546
+ ### Async Configuration (Recommended)
282
547
 
283
548
  ```typescript
284
- // main.ts
285
- import { DatabaseExceptionFilter } from "@ciscode/database-kit";
549
+ import { ConfigModule, ConfigService } from "@nestjs/config";
286
550
 
287
- app.useGlobalFilters(new DatabaseExceptionFilter());
551
+ DatabaseKitModule.forRootAsync({
552
+ imports: [ConfigModule],
553
+ useFactory: (config: ConfigService) => ({
554
+ config: {
555
+ type: config.get("DATABASE_TYPE") as "mongo" | "postgres",
556
+ connectionString: config.get("DATABASE_URL")!,
557
+ pool: {
558
+ min: config.get("DATABASE_POOL_MIN", 0),
559
+ max: config.get("DATABASE_POOL_MAX", 10),
560
+ },
561
+ },
562
+ }),
563
+ inject: [ConfigService],
564
+ });
288
565
  ```
289
566
 
290
- Or in a module:
567
+ ### Multiple Databases
291
568
 
292
569
  ```typescript
293
- import { APP_FILTER } from "@nestjs/core";
294
- import { DatabaseExceptionFilter } from "@ciscode/database-kit";
295
-
296
570
  @Module({
297
- providers: [{ provide: APP_FILTER, useClass: DatabaseExceptionFilter }],
571
+ imports: [
572
+ // Primary database
573
+ DatabaseKitModule.forRoot({
574
+ config: { type: "mongo", connectionString: process.env.MONGO_URI! },
575
+ }),
576
+ // Analytics database (PostgreSQL)
577
+ DatabaseKitModule.forFeature("ANALYTICS_DB", {
578
+ type: "postgres",
579
+ connectionString: process.env.ANALYTICS_DB_URL!,
580
+ }),
581
+ ],
298
582
  })
299
583
  export class AppModule {}
584
+
585
+ // Usage
586
+ @Injectable()
587
+ export class AnalyticsService {
588
+ constructor(
589
+ @InjectDatabaseByToken("ANALYTICS_DB")
590
+ private readonly analyticsDb: DatabaseService,
591
+ ) {}
592
+ }
593
+ ```
594
+
595
+ ---
596
+
597
+ ## πŸ›‘οΈ Error Handling
598
+
599
+ ### Global Exception Filter
600
+
601
+ ```typescript
602
+ // main.ts
603
+ import { DatabaseExceptionFilter } from "@ciscode/database-kit";
604
+
605
+ app.useGlobalFilters(new DatabaseExceptionFilter());
300
606
  ```
301
607
 
302
608
  ### Error Response Format
@@ -306,7 +612,7 @@ export class AppModule {}
306
612
  "statusCode": 409,
307
613
  "message": "A record with this value already exists",
308
614
  "error": "DuplicateKeyError",
309
- "timestamp": "2026-01-31T12:00:00.000Z",
615
+ "timestamp": "2026-02-01T12:00:00.000Z",
310
616
  "path": "/api/users"
311
617
  }
312
618
  ```
@@ -322,10 +628,15 @@ import {
322
628
  normalizePaginationOptions,
323
629
  parseSortString,
324
630
  calculateOffset,
631
+ createPageResult,
325
632
  } from "@ciscode/database-kit";
326
633
 
327
634
  const normalized = normalizePaginationOptions({ page: 1 });
635
+ // { page: 1, limit: 10, filter: {}, sort: undefined }
636
+
328
637
  const sortObj = parseSortString("-createdAt,name");
638
+ // { createdAt: -1, name: 1 }
639
+
329
640
  const offset = calculateOffset(2, 10); // 10
330
641
  ```
331
642
 
@@ -337,6 +648,7 @@ import {
337
648
  isValidUuid,
338
649
  sanitizeFilter,
339
650
  pickFields,
651
+ omitFields,
340
652
  } from "@ciscode/database-kit";
341
653
 
342
654
  isValidMongoId("507f1f77bcf86cd799439011"); // true
@@ -345,7 +657,8 @@ isValidUuid("550e8400-e29b-41d4-a716-446655440000"); // true
345
657
  const clean = sanitizeFilter({ name: "John", age: undefined });
346
658
  // { name: 'John' }
347
659
 
348
- const picked = pickFields(data, ["name", "email"]);
660
+ const picked = pickFields(user, ["name", "email"]);
661
+ const safe = omitFields(user, ["password", "secret"]);
349
662
  ```
350
663
 
351
664
  ---
@@ -358,17 +671,31 @@ npm test
358
671
 
359
672
  # Run with coverage
360
673
  npm run test:cov
674
+
675
+ # Run specific test file
676
+ npm test -- --testPathPattern=mongo.adapter.spec
361
677
  ```
362
678
 
363
679
  ### Mocking in Tests
364
680
 
365
681
  ```typescript
682
+ import { Test } from "@nestjs/testing";
683
+ import { DATABASE_TOKEN } from "@ciscode/database-kit";
684
+
685
+ const mockRepository = {
686
+ create: jest.fn().mockResolvedValue({ id: "1", name: "Test" }),
687
+ findById: jest.fn().mockResolvedValue({ id: "1", name: "Test" }),
688
+ findAll: jest.fn().mockResolvedValue([]),
689
+ findPage: jest
690
+ .fn()
691
+ .mockResolvedValue({ data: [], total: 0, page: 1, limit: 10, pages: 0 }),
692
+ updateById: jest.fn().mockResolvedValue({ id: "1", name: "Updated" }),
693
+ deleteById: jest.fn().mockResolvedValue(true),
694
+ };
695
+
366
696
  const mockDb = {
367
- createMongoRepository: jest.fn().mockReturnValue({
368
- create: jest.fn(),
369
- findById: jest.fn(),
370
- // ...
371
- }),
697
+ createMongoRepository: jest.fn().mockReturnValue(mockRepository),
698
+ createPostgresRepository: jest.fn().mockReturnValue(mockRepository),
372
699
  };
373
700
 
374
701
  const module = await Test.createTestingModule({
@@ -382,30 +709,42 @@ const module = await Test.createTestingModule({
382
709
 
383
710
  ```
384
711
  src/
385
- β”œβ”€β”€ index.ts # Public API exports
386
- β”œβ”€β”€ database-kit.module.ts # NestJS module
387
- β”œβ”€β”€ adapters/ # Database adapters
388
- β”‚ β”œβ”€β”€ mongo.adapter.ts
389
- β”‚ └── postgres.adapter.ts
390
- β”œβ”€β”€ config/ # Configuration
391
- β”‚ β”œβ”€β”€ database.config.ts
392
- β”‚ └── database.constants.ts
393
- β”œβ”€β”€ contracts/ # TypeScript contracts
394
- β”‚ └── database.contracts.ts
395
- β”œβ”€β”€ filters/ # Exception filters
396
- β”‚ └── database-exception.filter.ts
397
- β”œβ”€β”€ middleware/ # Decorators
398
- β”‚ └── database.decorators.ts
399
- β”œβ”€β”€ services/ # Business logic
400
- β”‚ β”œβ”€β”€ database.service.ts
401
- β”‚ └── logger.service.ts
402
- └── utils/ # Utilities
403
- β”œβ”€β”€ pagination.utils.ts
404
- └── validation.utils.ts
712
+ β”œβ”€β”€ index.ts # Public API exports
713
+ β”œβ”€β”€ database-kit.module.ts # NestJS module
714
+ β”œβ”€β”€ adapters/
715
+ β”‚ β”œβ”€β”€ mongo.adapter.ts # MongoDB implementation
716
+ β”‚ └── postgres.adapter.ts # PostgreSQL implementation
717
+ β”œβ”€β”€ config/
718
+ β”‚ β”œβ”€β”€ database.config.ts # Configuration helper
719
+ β”‚ └── database.constants.ts # Constants
720
+ β”œβ”€β”€ contracts/
721
+ β”‚ └── database.contracts.ts # TypeScript interfaces
722
+ β”œβ”€β”€ filters/
723
+ β”‚ └── database-exception.filter.ts # Error handling
724
+ β”œβ”€β”€ middleware/
725
+ β”‚ └── database.decorators.ts # DI decorators
726
+ β”œβ”€β”€ services/
727
+ β”‚ β”œβ”€β”€ database.service.ts # Main service
728
+ β”‚ └── logger.service.ts # Logging
729
+ └── utils/
730
+ β”œβ”€β”€ pagination.utils.ts # Pagination helpers
731
+ └── validation.utils.ts # Validation helpers
405
732
  ```
406
733
 
407
734
  ---
408
735
 
736
+ ## πŸ“Š Package Stats
737
+
738
+ | Metric | Value |
739
+ | ---------------- | ---------------------------- |
740
+ | **Version** | 1.0.0 |
741
+ | **Tests** | 133 passing |
742
+ | **Total LOC** | ~5,200 lines |
743
+ | **TypeScript** | 100% |
744
+ | **Dependencies** | Minimal (mongoose, knex, pg) |
745
+
746
+ ---
747
+
409
748
  ## πŸ”’ Security
410
749
 
411
750
  See [SECURITY.md](SECURITY.md) for:
@@ -435,12 +774,12 @@ See [CHANGELOG.md](CHANGELOG.md) for version history.
435
774
 
436
775
  ## πŸ“„ License
437
776
 
438
- MIT Β© [C International Service](https://ciscode.com)
777
+ MIT Β© [C International Service](https://ciscode.co.uk)
439
778
 
440
779
  ---
441
780
 
442
781
  ## πŸ™‹ Support
443
782
 
444
- - πŸ“§ Email: info@ciscode.com
783
+ - πŸ“§ Email: info@ciscod.com
445
784
  - πŸ› Issues: [GitHub Issues](https://github.com/CISCODE-MA/DatabaseKit/issues)
446
785
  - πŸ“– Docs: [GitHub Wiki](https://github.com/CISCODE-MA/DatabaseKit/wiki)