Thin MongoDB ODM built for Standard Schema
mongodb zod deno

findOneAnd

knotbin.com a9f7213d 903dfaab

verified
-642
PRODUCTION_READINESS_ASSESSMENT.md
··· 1 - # Production Readiness Assessment: Nozzle vs Mongoose 2 - 3 - ## Executive Summary 4 - 5 - **Current Status: Not Ready for Production** ⚠️ 6 - 7 - Nozzle is a promising lightweight ODM with excellent type safety, but it lacks several critical features required for production use compared to Mongoose. It's suitable for small projects or prototypes but needs significant enhancements before replacing Mongoose in production environments. 8 - 9 - --- 10 - 11 - ## ✅ Strengths 12 - 13 - ### 1. **Type Safety** 14 - - Excellent TypeScript integration with `InferModel` and `Input` (uses Zod's native types) 15 - - Type-safe operations throughout 16 - - Better type inference than Mongoose in many cases 17 - - Leverages Zod's built-in `z.input<T>` for input types (handles defaults automatically) 18 - 19 - ### 2. **Clean API** 20 - - Simple, intuitive API design 21 - - No decorators or magic - explicit and predictable 22 - - Minimal abstraction layer 23 - 24 - ### 3. **Schema Validation** 25 - - Uses Zod for schema validation 26 - - Validation on insert and update operations 27 - - Type-safe schema definitions 28 - 29 - ### 4. **Modern Stack** 30 - - Built on MongoDB native driver v6+ 31 - - Deno-first (can work with Node.js) 32 - - Lightweight dependencies 33 - 34 - --- 35 - 36 - ## ❌ Critical Missing Features for Production 37 - 38 - ### 1. **Transactions** ✅ IMPLEMENTED 39 - **Status:** ✅ **FULLY IMPLEMENTED** - Complete transaction support with MongoDB driver 40 - 41 - **Current Features:** 42 - - ✅ `withTransaction()` helper for automatic transaction management 43 - - ✅ `startSession()` and `endSession()` for manual session management 44 - - ✅ All Model methods accept `session` option 45 - - ✅ Automatic commit on success, abort on error 46 - - ✅ Support for TransactionOptions (read/write concern, etc.) 47 - - ✅ Clean API matching MongoDB best practices 48 - - ✅ Comprehensive documentation and examples 49 - 50 - **Nozzle API:** 51 - ```typescript 52 - // Automatic transaction management 53 - const result = await withTransaction(async (session) => { 54 - await UserModel.insertOne({ name: "Alice" }, { session }); 55 - await OrderModel.insertOne({ userId: "123" }, { session }); 56 - return { success: true }; 57 - }); 58 - 59 - // Manual session management 60 - const session = startSession(); 61 - try { 62 - await session.withTransaction(async () => { 63 - await UserModel.updateOne({...}, {...}, { session }); 64 - }); 65 - } finally { 66 - await endSession(session); 67 - } 68 - ``` 69 - 70 - **Supported Operations:** 71 - - ✅ Insert (insertOne, insertMany) 72 - - ✅ Find (find, findOne, findById) 73 - - ✅ Update (update, updateOne, replaceOne) 74 - - ✅ Delete (delete, deleteOne) 75 - - ✅ Aggregate 76 - - ✅ Count 77 - 78 - **Requirements:** 79 - - Requires MongoDB 4.0+ with Replica Set or MongoDB 4.2+ with Sharded Cluster 80 - - All operations must pass the session parameter 81 - 82 - --- 83 - 84 - ### 2. **Connection Management** 🟡 IMPORTANT 85 - **Status:** ✅ **SIGNIFICANTLY IMPROVED** - Connection pooling, retry logic, and health checks implemented 86 - 87 - **Current Features:** 88 - - ✅ Connection pooling configuration exposed via `MongoClientOptions` 89 - - ✅ Users can configure `maxPoolSize`, `minPoolSize`, `maxIdleTimeMS`, etc. 90 - - ✅ All MongoDB driver connection options available 91 - - ✅ Leverages MongoDB driver's built-in pooling (no custom implementation) 92 - - ✅ Automatic retry logic exposed (`retryReads`, `retryWrites`) 93 - - ✅ Health check functionality with response time monitoring 94 - - ✅ Comprehensive timeout configurations 95 - - ✅ Server health check intervals (`heartbeatFrequencyMS`) 96 - 97 - **Remaining Issues:** 98 - - ⚠️ No connection event handling (connected, disconnected, error events) 99 - - ⚠️ Cannot connect to multiple databases (singleton pattern) 100 - - ⚠️ No connection string validation 101 - - ⚠️ No manual reconnection API 102 - 103 - **Mongoose Provides:** 104 - - Automatic reconnection 105 - - Connection pool management (similar to what we expose) 106 - - Connection events (connected, error, disconnected) 107 - - Multiple database support 108 - - Connection options (readPreference, etc.) 109 - 110 - **Production Impact:** 111 - - ✅ Automatic retry on transient failures (reads and writes) 112 - - ✅ Health monitoring via `healthCheck()` function 113 - - ⚠️ Still cannot use multiple databases in same application 114 - - ⚠️ No event-driven connection state monitoring 115 - 116 - **Usage Example:** 117 - ```typescript 118 - await connect("mongodb://localhost:27017", "mydb", { 119 - // Connection pooling 120 - maxPoolSize: 10, 121 - minPoolSize: 2, 122 - 123 - // Automatic retry logic 124 - retryReads: true, 125 - retryWrites: true, 126 - 127 - // Timeouts 128 - connectTimeoutMS: 10000, 129 - socketTimeoutMS: 45000, 130 - serverSelectionTimeoutMS: 10000, 131 - 132 - // Resilience 133 - maxIdleTimeMS: 30000, 134 - heartbeatFrequencyMS: 10000, 135 - }); 136 - 137 - // Health check 138 - const health = await healthCheck(); 139 - if (!health.healthy) { 140 - console.error(`Database unhealthy: ${health.error}`); 141 - } 142 - ``` 143 - 144 - --- 145 - 146 - ### 3. **Middleware/Hooks** 🔴 CRITICAL 147 - **Status:** Not implemented 148 - 149 - **Missing:** 150 - - Pre/post save hooks 151 - - Pre/post remove hooks 152 - - Pre/post update hooks 153 - - Pre/post find hooks 154 - - Document methods 155 - - Static methods 156 - 157 - **Use Cases:** 158 - - Password hashing before save 159 - - Timestamp updates 160 - - Audit logging 161 - - Data transformation 162 - - Business logic encapsulation 163 - 164 - **Example Needed:** 165 - ```typescript 166 - // Pre-save hook for password hashing 167 - UserModel.pre('save', async function() { 168 - if (this.isModified('password')) { 169 - this.password = await hashPassword(this.password); 170 - } 171 - }); 172 - ``` 173 - 174 - --- 175 - 176 - ### 4. **Index Management** 🟡 IMPORTANT 177 - **Status:** ✅ **IMPLEMENTED** - Comprehensive index management API 178 - 179 - **Current Features:** 180 - - ✅ `createIndex()` - Create single index with options (unique, sparse, TTL, etc.) 181 - - ✅ `createIndexes()` - Create multiple indexes at once 182 - - ✅ `dropIndex()` - Drop a single index 183 - - ✅ `dropIndexes()` - Drop all indexes (except _id) 184 - - ✅ `listIndexes()` - List all indexes on collection 185 - - ✅ `getIndex()` - Get index information by name 186 - - ✅ `indexExists()` - Check if index exists 187 - - ✅ `syncIndexes()` - Synchronize indexes (create missing, update changed) 188 - - ✅ Support for compound indexes 189 - - ✅ Support for unique indexes 190 - - ✅ Support for text indexes (via MongoDB driver) 191 - - ✅ Support for geospatial indexes (via MongoDB driver) 192 - - ✅ Comprehensive test coverage (index_test.ts) 193 - 194 - **Remaining Gaps:** 195 - - ⚠️ No schema-level index definition (indexes defined programmatically, not in Zod schema) 196 - - ⚠️ No automatic index creation on model initialization 197 - - ⚠️ No index migration utilities (though `syncIndexes` helps) 198 - 199 - **Usage Example:** 200 - ```typescript 201 - // Create a unique index 202 - await UserModel.createIndex({ email: 1 }, { unique: true }); 203 - 204 - // Create compound index 205 - await UserModel.createIndex({ name: 1, age: -1 }); 206 - 207 - // Sync indexes (useful for migrations) 208 - await UserModel.syncIndexes([ 209 - { key: { email: 1 }, name: "email_idx", unique: true }, 210 - { key: { name: 1, age: -1 }, name: "name_age_idx" }, 211 - ]); 212 - ``` 213 - 214 - --- 215 - 216 - ### 5. **Update Validation** 🟡 IMPORTANT 217 - **Status:** ✅ **IMPLEMENTED** - Now validates updates using `parsePartial` 218 - 219 - **Current Behavior:** 220 - ```typescript 221 - // ✅ Now validates update data! 222 - await UserModel.updateOne({...}, { email: "invalid-email" }); // Throws validation error 223 - ``` 224 - 225 - **Implementation:** 226 - - `parsePartial` function validates partial update data (model.ts:33-57) 227 - - Both `update` and `updateOne` methods validate updates (model.ts:95-109) 228 - - Uses schema's `partial()` method if available (e.g., Zod) 229 - - Comprehensive tests confirm update validation works (validation_test.ts) 230 - 231 - **Remaining Gaps:** 232 - - No `setDefaultsOnInsert` option for updates 233 - - No `runValidators` toggle option 234 - - Validation errors still generic (not structured) 235 - 236 - --- 237 - 238 - ### 6. **Error Handling** 🟢 GOOD 239 - **Status:** ✅ **SIGNIFICANTLY IMPROVED** - Custom error classes with structured information 240 - 241 - **Current Features:** 242 - - ✅ Custom error class hierarchy (all extend `NozzleError`) 243 - - ✅ `ValidationError` with structured Zod issues 244 - - ✅ `ConnectionError` with URI context 245 - - ✅ `ConfigurationError` for invalid options 246 - - ✅ `DocumentNotFoundError` for missing documents 247 - - ✅ `OperationError` for database operation failures 248 - - ✅ `AsyncValidationError` for unsupported async validation 249 - - ✅ Field-specific error grouping via `getFieldErrors()` 250 - - ✅ Operation context (insert/update/replace) in validation errors 251 - - ✅ Proper error messages with context 252 - - ✅ Stack trace preservation 253 - 254 - **Remaining Gaps:** 255 - - ⚠️ No CastError equivalent (MongoDB driver handles this) 256 - - ⚠️ No custom MongoError wrapper (uses native MongoDB errors) 257 - - ⚠️ No error recovery utilities/strategies 258 - 259 - **Mongoose Comparison:** 260 - - ✅ ValidationError - Similar to Mongoose 261 - - ✅ Structured error details - Better than Mongoose (uses Zod issues) 262 - - ❌ CastError - Not implemented (less relevant with Zod) 263 - - ⚠️ MongoError - Uses native driver errors 264 - 265 - --- 266 - 267 - ### 7. **Default Values** 🟡 IMPORTANT 268 - **Status:** Partial support 269 - 270 - **Current Issues:** 271 - - Default values only work on insert if schema supports it 272 - - No `setDefaultsOnInsert` for updates 273 - - No function-based defaults with context 274 - - No conditional defaults 275 - 276 - --- 277 - 278 - ### 8. **Relationships/Population** 🟡 IMPORTANT 279 - **Status:** Not implemented 280 - 281 - **Missing:** 282 - - Document references 283 - - Population (join-like queries) 284 - - Virtual populate 285 - - Embedded documents management 286 - 287 - **Impact:** 288 - - Manual joins required 289 - - N+1 query problems 290 - - No relationship validation 291 - - Complex manual relationship management 292 - 293 - --- 294 - 295 - ### 9. **Query Building** 🟢 NICE TO HAVE 296 - **Status:** Basic MongoDB queries + pagination helper 297 - 298 - **Current Features:** 299 - - ✅ `findPaginated` method with skip, limit, and sort options (model.ts:138-149) 300 - - Basic MongoDB queries 301 - 302 - **Still Missing:** 303 - - Query builder API (fluent interface) 304 - - Query helpers 305 - - Query middleware 306 - - Query optimization hints 307 - 308 - **Mongoose Provides:** 309 - ```javascript 310 - UserModel.find() 311 - .where('age').gte(18) 312 - .where('name').equals('John') 313 - .select('name email') 314 - .limit(10) 315 - .sort({ createdAt: -1 }) 316 - ``` 317 - 318 - --- 319 - 320 - ### 10. **Plugins** 🟢 NICE TO HAVE 321 - **Status:** Not implemented 322 - 323 - **Missing:** 324 - - Plugin system 325 - - Reusable functionality 326 - - Ecosystem support 327 - 328 - --- 329 - 330 - ### 11. **Testing & Documentation** 🟡 IMPORTANT 331 - **Status:** ✅ **IMPROVED** - More comprehensive tests added 332 - 333 - **Current Coverage:** 334 - - ✅ CRUD operations (crud_test.ts) 335 - - ✅ Update validation (validation_test.ts) 336 - - ✅ Default values (features_test.ts) 337 - - ✅ Schema validation on insert 338 - - ✅ Update validation with various scenarios 339 - 340 - **Still Missing:** 341 - - Performance tests 342 - - Edge case testing (connection failures, concurrent operations) 343 - - API documentation 344 - - Migration guides 345 - - Best practices guide 346 - 347 - --- 348 - 349 - ### 12. **Production Features** 🟡 IMPORTANT 350 - **Implemented:** 351 - - ✅ Connection retry logic (`retryReads`, `retryWrites`) 352 - - ✅ Health check functionality (`healthCheck()`) 353 - 354 - **Missing:** 355 - - Graceful shutdown handling 356 - - Monitoring hooks/events 357 - - Performance metrics 358 - - Query logging 359 - - Slow query detection 360 - 361 - --- 362 - 363 - ## 🔍 Code Quality Issues 364 - 365 - ### 1. **Error Messages** 366 - ✅ **RESOLVED** - Now uses custom error classes: 367 - ```typescript 368 - // Current implementation 369 - throw new ValidationError(result.error.issues, "insert"); 370 - 371 - // Provides structured error with: 372 - // - Operation context (insert/update/replace) 373 - // - Zod issues array 374 - // - Field-specific error grouping via getFieldErrors() 375 - ``` 376 - 377 - ### 2. **Type Safety Gaps** 378 - ```typescript 379 - // This cast is unsafe 380 - validatedData as OptionalUnlessRequiredId<Infer<T>> 381 - ``` 382 - 383 - ### 3. **No Input Sanitization** 384 - - No protection against NoSQL injection 385 - - No query sanitization 386 - - Direct MongoDB query passthrough 387 - 388 - ### 4. **Connection State Management** 389 - ✅ **PARTIALLY RESOLVED** 390 - ```typescript 391 - // Now have health check 392 - const health = await healthCheck(); 393 - if (!health.healthy) { 394 - // Handle unhealthy connection 395 - } 396 - 397 - // Still missing: 398 - // - Connection state events 399 - // - Manual reconnection API 400 - ``` 401 - 402 - ### 5. **Async Validation Not Supported** 403 - ```typescript 404 - if (result instanceof Promise) { 405 - throw new Error("Async validation not supported"); 406 - } 407 - ``` 408 - 409 - --- 410 - 411 - ## 📊 Feature Comparison Matrix 412 - 413 - | Feature | Nozzle | Mongoose | Production Critical | 414 - |---------|--------|----------|-------------------| 415 - | Basic CRUD | ✅ | ✅ | ✅ | 416 - | Type Safety | ✅✅ | ✅ | ✅ | 417 - | Schema Validation | ✅ | ✅✅ | ✅ | 418 - | Transactions | ✅ | ✅ | 🔴 | 419 - | Middleware/Hooks | ❌ | ✅ | 🔴 | 420 - | Index Management | ✅ | ✅ | 🟡 | 421 - | Update Validation | ✅ | ✅ | 🟡 | 422 - | Relationships | ❌ | ✅ | 🟡 | 423 - | Connection Management | ✅ | ✅ | 🟡 | 424 - | Error Handling | ✅ | ✅ | 🟡 | 425 - | Plugins | ❌ | ✅ | 🟢 | 426 - | Query Builder | ⚠️ | ✅ | 🟢 | 427 - | Pagination | ✅ | ✅ | 🟢 | 428 - | Default Values | ⚠️ | ✅ | 🟡 | 429 - | Virtual Fields | ❌ | ✅ | 🟢 | 430 - | Methods/Statics | ❌ | ✅ | 🟡 | 431 - 432 - **Legend:** 433 - - ✅ = Fully implemented 434 - - ✅✅ = Better than Mongoose 435 - - ⚠️ = Partially implemented 436 - - ❌ = Not implemented 437 - - 🔴 = Critical for production 438 - - 🟡 = Important for production 439 - - 🟢 = Nice to have 440 - 441 - --- 442 - 443 - ## 🎯 Recommendations 444 - 445 - ### For Production Use 446 - 447 - **Do NOT use Nozzle in production if you need:** 448 - 1. Transactions 449 - 2. Complex relationships 450 - 3. Robust connection management 451 - 4. Middleware/hooks 452 - 5. Enterprise-level features 453 - 454 - **Consider Nozzle if:** 455 - 1. Building a simple CRUD API 456 - 2. Type safety is paramount 457 - 3. Minimal abstraction desired 458 - 4. Small to medium projects 459 - 5. Prototyping/MVP stage 460 - 461 - ### Migration Path 462 - 463 - If you want to make Nozzle production-ready: 464 - 465 - **Phase 1: Critical (Must Have)** ✅ **ALL COMPLETED** 466 - 1. ✅ **COMPLETED** - Implement transactions 467 - 2. ✅ **COMPLETED** - Add connection retry logic 468 - 3. ✅ **COMPLETED** - Improve error handling 469 - 4. ✅ **COMPLETED** - Add update validation 470 - 5. ✅ **COMPLETED** - Connection health checks 471 - 472 - **Phase 2: Important (Should Have)** 473 - 1. ❌ Middleware/hooks system 474 - 2. ✅ **COMPLETED** - Index management 475 - 3. ⚠️ Better default value handling (works via schema defaults) 476 - 4. ❌ Relationship support 477 - 5. ⚠️ Comprehensive testing (improved, but needs more edge cases) 478 - 479 - **Phase 3: Enhancement (Nice to Have)** 480 - 1. ✅ Plugin system 481 - 2. ✅ Query builder 482 - 3. ✅ Virtual fields 483 - 4. ✅ Methods/statics 484 - 5. ✅ Performance optimizations 485 - 486 - --- 487 - 488 - ## 📈 Production Readiness Score 489 - 490 - | Category | Score | Weight | Weighted Score | 491 - |----------|-------|--------|----------------| 492 - | Core Functionality | 8/10 | 20% | 1.6 | 493 - | Type Safety | 9/10 | 15% | 1.35 | 494 - | Error Handling | 8/10 | 15% | 1.2 | 495 - | Connection Management | 7/10 | 15% | 1.05 | 496 - | Advanced Features | 5/10 | 20% | 1.0 | 497 - | Testing & Docs | 7/10 | 10% | 0.7 | 498 - | Production Features | 5/10 | 5% | 0.25 | 499 - 500 - **Overall Score: 7.15/10** (Production Ready for Most Use Cases) 501 - 502 - **Mongoose Equivalent Score: ~8.5/10** 503 - 504 - --- 505 - 506 - ## 🚀 Conclusion 507 - 508 - Nozzle is an excellent **proof of concept** and **development tool** with superior type safety, but it's **not ready to replace Mongoose in production** without significant development work. 509 - 510 - **Estimated effort to reach production parity:** 3-6 months of full-time development 511 - 512 - **Recommendation:** Use Mongoose for production, or invest heavily in Nozzle development before considering it as a replacement. 513 - 514 - --- 515 - 516 - ## 📝 Specific Code Issues Found 517 - 518 - 1. **model.ts:28** - Generic error messages, no structured error types 519 - 2. **model.ts:24-26** - Async validation explicitly unsupported (throws error) 520 - 3. **model.ts:71, 78, 118** - Unsafe type casting (`as OptionalUnlessRequiredId`) 521 - 4. ✅ **FIXED** - **model.ts:95-109** - Update operations now validate input via `parsePartial` 522 - 5. ✅ **FIXED** - All update methods (`update`, `updateOne`, `replaceOne`) now validate consistently 523 - +6. ✅ **COMPLETED** - **client.ts** - Connection pooling and retry logic now fully exposed via `ConnectOptions` 524 - 7. ⚠️ **client.ts** - No way to manually reconnect if connection is lost (automatic retry handles most cases) 525 - 8. **client.ts** - Singleton pattern prevents multiple database connections 526 - 9. **No transaction support** - Critical for data consistency 527 - 10. **No query sanitization** - Direct MongoDB query passthrough (potential NoSQL injection) 528 - 11. ✅ **FIXED** - Removed `InsertType` in favor of Zod's native `z.input<T>` which handles defaults generically 529 - 12. **No error recovery** - Application will crash on connection loss 530 - 531 - ## 🆕 Recent Improvements 532 - 533 - 1. ✅ **Transaction Support Implemented** (client.ts, model.ts) 534 - - `withTransaction()` helper for automatic transaction management 535 - - `startSession()` and `endSession()` for manual control 536 - - All Model methods accept session options 537 - - Automatic commit/abort handling 538 - - Support for TransactionOptions 539 - - Clean API matching MongoDB best practices 540 - - Comprehensive documentation with examples 541 - - Works with MongoDB 4.0+ replica sets and 4.2+ sharded clusters 542 - 543 - 2. ✅ **Structured Error Handling Implemented** (errors.ts) 544 - - Custom error class hierarchy with `NozzleError` base class 545 - - `ValidationError` with Zod issue integration and field grouping 546 - - `ConnectionError` with URI context 547 - - `ConfigurationError`, `DocumentNotFoundError`, `OperationError` 548 - - Operation-specific validation errors (insert/update/replace) 549 - - `getFieldErrors()` method for field-specific error handling 550 - - Comprehensive test coverage (errors_test.ts - 10 tests) 551 - - Improved error messages with context 552 - 553 - 2. ✅ **Connection Retry Logic Implemented** (client.ts) 554 - - Automatic retry for reads and writes via `retryReads` and `retryWrites` 555 - - Full MongoDB driver connection options exposed 556 - - Production-ready resilience configuration 557 - - Comprehensive test coverage (connection_test.ts) 558 - 559 - 3. ✅ **Health Check Functionality Added** (client.ts) 560 - - `healthCheck()` function for connection monitoring 561 - - Response time measurement 562 - - Detailed health status reporting 563 - - Test coverage included 564 - 565 - 4. ✅ **Connection Pooling Exposed** (client.ts) 566 - - Connection pooling options now available via `MongoClientOptions` 567 - - Users can configure all MongoDB driver connection options 568 - - Comprehensive test coverage (connection_test.ts) 569 - 570 - 5. ✅ **Update Validation Implemented** (model.ts:33-57, 95-109) 571 - - `parsePartial` function validates partial update data 572 - - Both `update` and `updateOne` methods now validate 573 - - Comprehensive test coverage added 574 - 575 - 6. ✅ **Pagination Support Added** (model.ts:138-149) 576 - - `findPaginated` method with skip, limit, and sort options 577 - - Convenient helper for common pagination needs 578 - 579 - 7. ✅ **Index Management Implemented** (model.ts:147-250) 580 - - Full index management API: createIndex, createIndexes, dropIndex, dropIndexes 581 - - Index querying: listIndexes, getIndex, indexExists 582 - - Index synchronization: syncIndexes for migrations 583 - - Support for all MongoDB index types (unique, compound, text, geospatial) 584 - - Comprehensive test coverage (index_test.ts) 585 - 586 - 8. ✅ **Enhanced Test Coverage** 587 - - CRUD operations testing 588 - - Update validation testing 589 - - Default values testing 590 - - Index management testing 591 - - Connection retry and resilience testing 592 - - Health check testing 593 - - Error handling testing (10 comprehensive tests) 594 - 595 - --- 596 - 597 - *Assessment Date: 2024* 598 - *Last Updated: 2024* 599 - *Assessed by: AI Code Review* 600 - *Version: 0.2.0* 601 - 602 - ## 📋 Changelog 603 - 604 - ### Version 0.5.0 (Latest) 605 - - ✅ **TRANSACTIONS IMPLEMENTED** - Full transaction support 606 - - ✅ `withTransaction()` helper for automatic transaction management 607 - - ✅ All Model methods accept session options 608 - - ✅ Automatic commit/abort handling 609 - - ✅ Phase 1 Critical Features: **ALL COMPLETED** 🎉 610 - - Updated scores (7.15/10, up from 6.55/10) 611 - - Advanced Features upgraded from 2/10 to 5/10 612 - - **Production Ready** status achieved for most use cases 613 - 614 - ### Version 0.4.0 615 - - ✅ Structured error handling implemented (custom error classes) 616 - - ✅ `ValidationError` with field-specific error grouping 617 - - ✅ `ConnectionError`, `ConfigurationError`, and other error types 618 - - ✅ Operation context in validation errors (insert/update/replace) 619 - - ✅ 10 comprehensive error handling tests added 620 - - Updated scores (6.55/10, up from 5.85/10) 621 - - Error Handling upgraded from 4/10 to 8/10 622 - - Testing & Docs upgraded from 6/10 to 7/10 623 - 624 - ### Version 0.3.0 625 - - ✅ Connection retry logic implemented (`retryReads`, `retryWrites`) 626 - - ✅ Health check functionality added (`healthCheck()`) 627 - - ✅ Full production resilience configuration support 628 - - Updated scores (5.85/10, up from 5.1/10) 629 - - Connection Management upgraded from 3/10 to 7/10 630 - - Production Features upgraded from 2/10 to 5/10 631 - 632 - ### Version 0.2.0 633 - - ✅ Update validation now implemented 634 - - ✅ Pagination support added (`findPaginated`) 635 - - ✅ Index management implemented 636 - - ✅ Connection pooling options exposed 637 - - ✅ Enhanced test coverage 638 - - Updated scores and feature matrix 639 - - Fixed incorrect code issue reports 640 - 641 - ### Version 0.1.0 (Initial) 642 - - Initial production readiness assessment
+1 -1
README.md
··· 271 271 - [x] Index management 272 272 - [ ] Middleware/hooks system 273 273 - [ ] Relationship/population support 274 - - [ ] Better default value handling 274 + - [x] Better default value handling 275 275 - [ ] Comprehensive edge case testing 276 276 277 277 ### 🟢 Nice to Have
+60
model/core.ts
··· 10 10 FindOptions, 11 11 UpdateOptions, 12 12 ReplaceOptions, 13 + FindOneAndUpdateOptions, 14 + FindOneAndReplaceOptions, 13 15 DeleteOptions, 14 16 CountDocumentsOptions, 15 17 AggregateOptions, ··· 18 20 WithId, 19 21 BulkWriteOptions, 20 22 UpdateFilter, 23 + ModifyResult, 21 24 } from "mongodb"; 22 25 import { ObjectId } from "mongodb"; 23 26 import type { Schema, Infer, Input } from "../types.ts"; ··· 222 225 query, 223 226 withoutId as Infer<T>, 224 227 options 228 + ); 229 + } 230 + 231 + /** 232 + * Find a single document and update it 233 + * 234 + * Case handling: 235 + * - If upsert: false (or undefined) → Normal update 236 + * - If upsert: true → Defaults added to $setOnInsert for new document creation 237 + */ 238 + export async function findOneAndUpdate<T extends Schema>( 239 + collection: Collection<Infer<T>>, 240 + schema: T, 241 + query: Filter<Infer<T>>, 242 + data: Partial<z.infer<T>>, 243 + options?: FindOneAndUpdateOptions 244 + ): Promise<ModifyResult<Infer<T>>> { 245 + const validatedData = parsePartial(schema, data); 246 + let updateDoc: UpdateFilter<Infer<T>> = { $set: validatedData as Partial<Infer<T>> }; 247 + 248 + if (options?.upsert) { 249 + updateDoc = applyDefaultsForUpsert(schema, query, updateDoc); 250 + } 251 + 252 + const resolvedOptions: FindOneAndUpdateOptions & { includeResultMetadata: true } = { 253 + ...(options ?? {}), 254 + includeResultMetadata: true as const, 255 + }; 256 + 257 + return await collection.findOneAndUpdate(query, updateDoc, resolvedOptions); 258 + } 259 + 260 + /** 261 + * Find a single document and replace it 262 + * 263 + * Defaults are applied via parseReplace(), which fills in missing fields 264 + * for both normal replacements and upsert-created documents. 265 + */ 266 + export async function findOneAndReplace<T extends Schema>( 267 + collection: Collection<Infer<T>>, 268 + schema: T, 269 + query: Filter<Infer<T>>, 270 + data: Input<T>, 271 + options?: FindOneAndReplaceOptions 272 + ): Promise<ModifyResult<Infer<T>>> { 273 + const validatedData = parseReplace(schema, data); 274 + const { _id, ...withoutId } = validatedData as Infer<T> & { _id?: unknown }; 275 + 276 + const resolvedOptions: FindOneAndReplaceOptions & { includeResultMetadata: true } = { 277 + ...(options ?? {}), 278 + includeResultMetadata: true as const, 279 + }; 280 + 281 + return await collection.findOneAndReplace( 282 + query, 283 + withoutId as Infer<T>, 284 + resolvedOptions 225 285 ); 226 286 } 227 287
+35
model/index.ts
··· 14 14 FindOptions, 15 15 UpdateOptions, 16 16 ReplaceOptions, 17 + FindOneAndUpdateOptions, 18 + FindOneAndReplaceOptions, 17 19 DeleteOptions, 18 20 CountDocumentsOptions, 19 21 AggregateOptions, ··· 21 23 UpdateResult, 22 24 WithId, 23 25 BulkWriteOptions, 26 + ModifyResult, 24 27 } from "mongodb"; 25 28 import type { ObjectId } from "mongodb"; 26 29 import { getDb } from "../client/connection.ts"; ··· 174 177 } 175 178 176 179 /** 180 + * Find a single document and update it 181 + * 182 + * @param query - MongoDB query filter 183 + * @param data - Partial data to update 184 + * @param options - FindOneAndUpdate options (including upsert and returnDocument) 185 + * @returns Modify result containing the matched document 186 + */ 187 + async findOneAndUpdate( 188 + query: Filter<Infer<T>>, 189 + data: Partial<z.infer<T>>, 190 + options?: FindOneAndUpdateOptions 191 + ): Promise<ModifyResult<Infer<T>>> { 192 + return await core.findOneAndUpdate(this.collection, this.schema, query, data, options); 193 + } 194 + 195 + /** 177 196 * Replace a single document matching the query 178 197 * 179 198 * @param query - MongoDB query filter ··· 187 206 options?: ReplaceOptions 188 207 ): Promise<UpdateResult<Infer<T>>> { 189 208 return await core.replaceOne(this.collection, this.schema, query, data, options); 209 + } 210 + 211 + /** 212 + * Find a single document and replace it 213 + * 214 + * @param query - MongoDB query filter 215 + * @param data - Complete document data for replacement 216 + * @param options - FindOneAndReplace options (including upsert and returnDocument) 217 + * @returns Modify result containing the matched document 218 + */ 219 + async findOneAndReplace( 220 + query: Filter<Infer<T>>, 221 + data: Input<T>, 222 + options?: FindOneAndReplaceOptions 223 + ): Promise<ModifyResult<Infer<T>>> { 224 + return await core.findOneAndReplace(this.collection, this.schema, query, data, options); 190 225 } 191 226 192 227 /**
+51 -4
tests/defaults_test.ts
··· 1 1 import { assertEquals, assertExists } from "@std/assert"; 2 2 import { z } from "@zod/zod"; 3 - import { connect, disconnect, Model, type Input } from "../mod.ts"; 3 + import { connect, disconnect, Model } from "../mod.ts"; 4 4 import { applyDefaultsForUpsert } from "../model/validation.ts"; 5 5 import { MongoMemoryServer } from "mongodb-memory-server-core"; 6 6 ··· 24 24 createdAt: z.date().default(() => new Date("2024-01-01T00:00:00Z")), 25 25 tags: z.array(z.string()).default([]), 26 26 }); 27 - 28 - type Product = z.infer<typeof productSchema>; 29 - type ProductInsert = Input<typeof productSchema>; 30 27 31 28 let ProductModel: Model<typeof productSchema>; 32 29 let mongoServer: MongoMemoryServer; ··· 350 347 sanitizeResources: false, 351 348 sanitizeOps: false, 352 349 }); 350 + 351 + Deno.test({ 352 + name: "Defaults: findOneAndUpdate with upsert preserves query equality fields", 353 + async fn() { 354 + await ProductModel.findOneAndUpdate( 355 + { name: "FindOneUpsert", category: "special" }, 356 + { price: 12.5 }, 357 + { upsert: true } 358 + ); 359 + 360 + const product = await ProductModel.findOne({ name: "FindOneUpsert" }); 361 + assertExists(product); 362 + 363 + assertEquals(product.category, "special"); // from query, not default 364 + assertEquals(product.price, 12.5); // from update 365 + assertEquals(product.inStock, true); // default applied 366 + assertExists(product.createdAt); // default applied 367 + assertEquals(product.tags, []); // default applied 368 + }, 369 + sanitizeResources: false, 370 + sanitizeOps: false, 371 + }); 372 + 373 + Deno.test({ 374 + name: "Defaults: findOneAndReplace with upsert applies defaults on creation", 375 + async fn() { 376 + const result = await ProductModel.findOneAndReplace( 377 + { name: "FindOneReplaceUpsert" }, 378 + { 379 + name: "FindOneReplaceUpsert", 380 + price: 77.0, 381 + }, 382 + { upsert: true } 383 + ); 384 + 385 + assertExists(result.lastErrorObject?.upserted); 386 + 387 + const product = await ProductModel.findOne({ name: "FindOneReplaceUpsert" }); 388 + assertExists(product); 389 + 390 + assertEquals(product.name, "FindOneReplaceUpsert"); 391 + assertEquals(product.price, 77.0); 392 + assertEquals(product.category, "general"); // default applied 393 + assertEquals(product.inStock, true); // default applied 394 + assertExists(product.createdAt); // default applied 395 + assertEquals(product.tags, []); // default applied 396 + }, 397 + sanitizeResources: false, 398 + sanitizeOps: false, 399 + });
+1 -1
tests/errors_test.ts
··· 32 32 // Test schemas 33 33 const userSchema = z.object({ 34 34 name: z.string().min(1), 35 - email: z.string().email(), 35 + email: z.email(), 36 36 age: z.number().int().positive().optional(), 37 37 }); 38 38