Thin MongoDB ODM built for Standard Schema
mongodb zod deno

findOneAnd

knotbin.com a9f7213d 903dfaab

verified
-642
PRODUCTION_READINESS_ASSESSMENT.md
···
-
# Production Readiness Assessment: Nozzle vs Mongoose
-
-
## Executive Summary
-
-
**Current Status: Not Ready for Production** ⚠️
-
-
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.
-
-
---
-
-
## ✅ Strengths
-
-
### 1. **Type Safety**
-
- Excellent TypeScript integration with `InferModel` and `Input` (uses Zod's native types)
-
- Type-safe operations throughout
-
- Better type inference than Mongoose in many cases
-
- Leverages Zod's built-in `z.input<T>` for input types (handles defaults automatically)
-
-
### 2. **Clean API**
-
- Simple, intuitive API design
-
- No decorators or magic - explicit and predictable
-
- Minimal abstraction layer
-
-
### 3. **Schema Validation**
-
- Uses Zod for schema validation
-
- Validation on insert and update operations
-
- Type-safe schema definitions
-
-
### 4. **Modern Stack**
-
- Built on MongoDB native driver v6+
-
- Deno-first (can work with Node.js)
-
- Lightweight dependencies
-
-
---
-
-
## ❌ Critical Missing Features for Production
-
-
### 1. **Transactions** ✅ IMPLEMENTED
-
**Status:** ✅ **FULLY IMPLEMENTED** - Complete transaction support with MongoDB driver
-
-
**Current Features:**
-
- ✅ `withTransaction()` helper for automatic transaction management
-
- ✅ `startSession()` and `endSession()` for manual session management
-
- ✅ All Model methods accept `session` option
-
- ✅ Automatic commit on success, abort on error
-
- ✅ Support for TransactionOptions (read/write concern, etc.)
-
- ✅ Clean API matching MongoDB best practices
-
- ✅ Comprehensive documentation and examples
-
-
**Nozzle API:**
-
```typescript
-
// Automatic transaction management
-
const result = await withTransaction(async (session) => {
-
await UserModel.insertOne({ name: "Alice" }, { session });
-
await OrderModel.insertOne({ userId: "123" }, { session });
-
return { success: true };
-
});
-
-
// Manual session management
-
const session = startSession();
-
try {
-
await session.withTransaction(async () => {
-
await UserModel.updateOne({...}, {...}, { session });
-
});
-
} finally {
-
await endSession(session);
-
}
-
```
-
-
**Supported Operations:**
-
- ✅ Insert (insertOne, insertMany)
-
- ✅ Find (find, findOne, findById)
-
- ✅ Update (update, updateOne, replaceOne)
-
- ✅ Delete (delete, deleteOne)
-
- ✅ Aggregate
-
- ✅ Count
-
-
**Requirements:**
-
- Requires MongoDB 4.0+ with Replica Set or MongoDB 4.2+ with Sharded Cluster
-
- All operations must pass the session parameter
-
-
---
-
-
### 2. **Connection Management** 🟡 IMPORTANT
-
**Status:** ✅ **SIGNIFICANTLY IMPROVED** - Connection pooling, retry logic, and health checks implemented
-
-
**Current Features:**
-
- ✅ Connection pooling configuration exposed via `MongoClientOptions`
-
- ✅ Users can configure `maxPoolSize`, `minPoolSize`, `maxIdleTimeMS`, etc.
-
- ✅ All MongoDB driver connection options available
-
- ✅ Leverages MongoDB driver's built-in pooling (no custom implementation)
-
- ✅ Automatic retry logic exposed (`retryReads`, `retryWrites`)
-
- ✅ Health check functionality with response time monitoring
-
- ✅ Comprehensive timeout configurations
-
- ✅ Server health check intervals (`heartbeatFrequencyMS`)
-
-
**Remaining Issues:**
-
- ⚠️ No connection event handling (connected, disconnected, error events)
-
- ⚠️ Cannot connect to multiple databases (singleton pattern)
-
- ⚠️ No connection string validation
-
- ⚠️ No manual reconnection API
-
-
**Mongoose Provides:**
-
- Automatic reconnection
-
- Connection pool management (similar to what we expose)
-
- Connection events (connected, error, disconnected)
-
- Multiple database support
-
- Connection options (readPreference, etc.)
-
-
**Production Impact:**
-
- ✅ Automatic retry on transient failures (reads and writes)
-
- ✅ Health monitoring via `healthCheck()` function
-
- ⚠️ Still cannot use multiple databases in same application
-
- ⚠️ No event-driven connection state monitoring
-
-
**Usage Example:**
-
```typescript
-
await connect("mongodb://localhost:27017", "mydb", {
-
// Connection pooling
-
maxPoolSize: 10,
-
minPoolSize: 2,
-
-
// Automatic retry logic
-
retryReads: true,
-
retryWrites: true,
-
-
// Timeouts
-
connectTimeoutMS: 10000,
-
socketTimeoutMS: 45000,
-
serverSelectionTimeoutMS: 10000,
-
-
// Resilience
-
maxIdleTimeMS: 30000,
-
heartbeatFrequencyMS: 10000,
-
});
-
-
// Health check
-
const health = await healthCheck();
-
if (!health.healthy) {
-
console.error(`Database unhealthy: ${health.error}`);
-
}
-
```
-
-
---
-
-
### 3. **Middleware/Hooks** 🔴 CRITICAL
-
**Status:** Not implemented
-
-
**Missing:**
-
- Pre/post save hooks
-
- Pre/post remove hooks
-
- Pre/post update hooks
-
- Pre/post find hooks
-
- Document methods
-
- Static methods
-
-
**Use Cases:**
-
- Password hashing before save
-
- Timestamp updates
-
- Audit logging
-
- Data transformation
-
- Business logic encapsulation
-
-
**Example Needed:**
-
```typescript
-
// Pre-save hook for password hashing
-
UserModel.pre('save', async function() {
-
if (this.isModified('password')) {
-
this.password = await hashPassword(this.password);
-
}
-
});
-
```
-
-
---
-
-
### 4. **Index Management** 🟡 IMPORTANT
-
**Status:** ✅ **IMPLEMENTED** - Comprehensive index management API
-
-
**Current Features:**
-
- ✅ `createIndex()` - Create single index with options (unique, sparse, TTL, etc.)
-
- ✅ `createIndexes()` - Create multiple indexes at once
-
- ✅ `dropIndex()` - Drop a single index
-
- ✅ `dropIndexes()` - Drop all indexes (except _id)
-
- ✅ `listIndexes()` - List all indexes on collection
-
- ✅ `getIndex()` - Get index information by name
-
- ✅ `indexExists()` - Check if index exists
-
- ✅ `syncIndexes()` - Synchronize indexes (create missing, update changed)
-
- ✅ Support for compound indexes
-
- ✅ Support for unique indexes
-
- ✅ Support for text indexes (via MongoDB driver)
-
- ✅ Support for geospatial indexes (via MongoDB driver)
-
- ✅ Comprehensive test coverage (index_test.ts)
-
-
**Remaining Gaps:**
-
- ⚠️ No schema-level index definition (indexes defined programmatically, not in Zod schema)
-
- ⚠️ No automatic index creation on model initialization
-
- ⚠️ No index migration utilities (though `syncIndexes` helps)
-
-
**Usage Example:**
-
```typescript
-
// Create a unique index
-
await UserModel.createIndex({ email: 1 }, { unique: true });
-
-
// Create compound index
-
await UserModel.createIndex({ name: 1, age: -1 });
-
-
// Sync indexes (useful for migrations)
-
await UserModel.syncIndexes([
-
{ key: { email: 1 }, name: "email_idx", unique: true },
-
{ key: { name: 1, age: -1 }, name: "name_age_idx" },
-
]);
-
```
-
-
---
-
-
### 5. **Update Validation** 🟡 IMPORTANT
-
**Status:** ✅ **IMPLEMENTED** - Now validates updates using `parsePartial`
-
-
**Current Behavior:**
-
```typescript
-
// ✅ Now validates update data!
-
await UserModel.updateOne({...}, { email: "invalid-email" }); // Throws validation error
-
```
-
-
**Implementation:**
-
- `parsePartial` function validates partial update data (model.ts:33-57)
-
- Both `update` and `updateOne` methods validate updates (model.ts:95-109)
-
- Uses schema's `partial()` method if available (e.g., Zod)
-
- Comprehensive tests confirm update validation works (validation_test.ts)
-
-
**Remaining Gaps:**
-
- No `setDefaultsOnInsert` option for updates
-
- No `runValidators` toggle option
-
- Validation errors still generic (not structured)
-
-
---
-
-
### 6. **Error Handling** 🟢 GOOD
-
**Status:** ✅ **SIGNIFICANTLY IMPROVED** - Custom error classes with structured information
-
-
**Current Features:**
-
- ✅ Custom error class hierarchy (all extend `NozzleError`)
-
- ✅ `ValidationError` with structured Zod issues
-
- ✅ `ConnectionError` with URI context
-
- ✅ `ConfigurationError` for invalid options
-
- ✅ `DocumentNotFoundError` for missing documents
-
- ✅ `OperationError` for database operation failures
-
- ✅ `AsyncValidationError` for unsupported async validation
-
- ✅ Field-specific error grouping via `getFieldErrors()`
-
- ✅ Operation context (insert/update/replace) in validation errors
-
- ✅ Proper error messages with context
-
- ✅ Stack trace preservation
-
-
**Remaining Gaps:**
-
- ⚠️ No CastError equivalent (MongoDB driver handles this)
-
- ⚠️ No custom MongoError wrapper (uses native MongoDB errors)
-
- ⚠️ No error recovery utilities/strategies
-
-
**Mongoose Comparison:**
-
- ✅ ValidationError - Similar to Mongoose
-
- ✅ Structured error details - Better than Mongoose (uses Zod issues)
-
- ❌ CastError - Not implemented (less relevant with Zod)
-
- ⚠️ MongoError - Uses native driver errors
-
-
---
-
-
### 7. **Default Values** 🟡 IMPORTANT
-
**Status:** Partial support
-
-
**Current Issues:**
-
- Default values only work on insert if schema supports it
-
- No `setDefaultsOnInsert` for updates
-
- No function-based defaults with context
-
- No conditional defaults
-
-
---
-
-
### 8. **Relationships/Population** 🟡 IMPORTANT
-
**Status:** Not implemented
-
-
**Missing:**
-
- Document references
-
- Population (join-like queries)
-
- Virtual populate
-
- Embedded documents management
-
-
**Impact:**
-
- Manual joins required
-
- N+1 query problems
-
- No relationship validation
-
- Complex manual relationship management
-
-
---
-
-
### 9. **Query Building** 🟢 NICE TO HAVE
-
**Status:** Basic MongoDB queries + pagination helper
-
-
**Current Features:**
-
- ✅ `findPaginated` method with skip, limit, and sort options (model.ts:138-149)
-
- Basic MongoDB queries
-
-
**Still Missing:**
-
- Query builder API (fluent interface)
-
- Query helpers
-
- Query middleware
-
- Query optimization hints
-
-
**Mongoose Provides:**
-
```javascript
-
UserModel.find()
-
.where('age').gte(18)
-
.where('name').equals('John')
-
.select('name email')
-
.limit(10)
-
.sort({ createdAt: -1 })
-
```
-
-
---
-
-
### 10. **Plugins** 🟢 NICE TO HAVE
-
**Status:** Not implemented
-
-
**Missing:**
-
- Plugin system
-
- Reusable functionality
-
- Ecosystem support
-
-
---
-
-
### 11. **Testing & Documentation** 🟡 IMPORTANT
-
**Status:** ✅ **IMPROVED** - More comprehensive tests added
-
-
**Current Coverage:**
-
- ✅ CRUD operations (crud_test.ts)
-
- ✅ Update validation (validation_test.ts)
-
- ✅ Default values (features_test.ts)
-
- ✅ Schema validation on insert
-
- ✅ Update validation with various scenarios
-
-
**Still Missing:**
-
- Performance tests
-
- Edge case testing (connection failures, concurrent operations)
-
- API documentation
-
- Migration guides
-
- Best practices guide
-
-
---
-
-
### 12. **Production Features** 🟡 IMPORTANT
-
**Implemented:**
-
- ✅ Connection retry logic (`retryReads`, `retryWrites`)
-
- ✅ Health check functionality (`healthCheck()`)
-
-
**Missing:**
-
- Graceful shutdown handling
-
- Monitoring hooks/events
-
- Performance metrics
-
- Query logging
-
- Slow query detection
-
-
---
-
-
## 🔍 Code Quality Issues
-
-
### 1. **Error Messages**
-
✅ **RESOLVED** - Now uses custom error classes:
-
```typescript
-
// Current implementation
-
throw new ValidationError(result.error.issues, "insert");
-
-
// Provides structured error with:
-
// - Operation context (insert/update/replace)
-
// - Zod issues array
-
// - Field-specific error grouping via getFieldErrors()
-
```
-
-
### 2. **Type Safety Gaps**
-
```typescript
-
// This cast is unsafe
-
validatedData as OptionalUnlessRequiredId<Infer<T>>
-
```
-
-
### 3. **No Input Sanitization**
-
- No protection against NoSQL injection
-
- No query sanitization
-
- Direct MongoDB query passthrough
-
-
### 4. **Connection State Management**
-
✅ **PARTIALLY RESOLVED**
-
```typescript
-
// Now have health check
-
const health = await healthCheck();
-
if (!health.healthy) {
-
// Handle unhealthy connection
-
}
-
-
// Still missing:
-
// - Connection state events
-
// - Manual reconnection API
-
```
-
-
### 5. **Async Validation Not Supported**
-
```typescript
-
if (result instanceof Promise) {
-
throw new Error("Async validation not supported");
-
}
-
```
-
-
---
-
-
## 📊 Feature Comparison Matrix
-
-
| Feature | Nozzle | Mongoose | Production Critical |
-
|---------|--------|----------|-------------------|
-
| Basic CRUD | ✅ | ✅ | ✅ |
-
| Type Safety | ✅✅ | ✅ | ✅ |
-
| Schema Validation | ✅ | ✅✅ | ✅ |
-
| Transactions | ✅ | ✅ | 🔴 |
-
| Middleware/Hooks | ❌ | ✅ | 🔴 |
-
| Index Management | ✅ | ✅ | 🟡 |
-
| Update Validation | ✅ | ✅ | 🟡 |
-
| Relationships | ❌ | ✅ | 🟡 |
-
| Connection Management | ✅ | ✅ | 🟡 |
-
| Error Handling | ✅ | ✅ | 🟡 |
-
| Plugins | ❌ | ✅ | 🟢 |
-
| Query Builder | ⚠️ | ✅ | 🟢 |
-
| Pagination | ✅ | ✅ | 🟢 |
-
| Default Values | ⚠️ | ✅ | 🟡 |
-
| Virtual Fields | ❌ | ✅ | 🟢 |
-
| Methods/Statics | ❌ | ✅ | 🟡 |
-
-
**Legend:**
-
- ✅ = Fully implemented
-
- ✅✅ = Better than Mongoose
-
- ⚠️ = Partially implemented
-
- ❌ = Not implemented
-
- 🔴 = Critical for production
-
- 🟡 = Important for production
-
- 🟢 = Nice to have
-
-
---
-
-
## 🎯 Recommendations
-
-
### For Production Use
-
-
**Do NOT use Nozzle in production if you need:**
-
1. Transactions
-
2. Complex relationships
-
3. Robust connection management
-
4. Middleware/hooks
-
5. Enterprise-level features
-
-
**Consider Nozzle if:**
-
1. Building a simple CRUD API
-
2. Type safety is paramount
-
3. Minimal abstraction desired
-
4. Small to medium projects
-
5. Prototyping/MVP stage
-
-
### Migration Path
-
-
If you want to make Nozzle production-ready:
-
-
**Phase 1: Critical (Must Have)** ✅ **ALL COMPLETED**
-
1. ✅ **COMPLETED** - Implement transactions
-
2. ✅ **COMPLETED** - Add connection retry logic
-
3. ✅ **COMPLETED** - Improve error handling
-
4. ✅ **COMPLETED** - Add update validation
-
5. ✅ **COMPLETED** - Connection health checks
-
-
**Phase 2: Important (Should Have)**
-
1. ❌ Middleware/hooks system
-
2. ✅ **COMPLETED** - Index management
-
3. ⚠️ Better default value handling (works via schema defaults)
-
4. ❌ Relationship support
-
5. ⚠️ Comprehensive testing (improved, but needs more edge cases)
-
-
**Phase 3: Enhancement (Nice to Have)**
-
1. ✅ Plugin system
-
2. ✅ Query builder
-
3. ✅ Virtual fields
-
4. ✅ Methods/statics
-
5. ✅ Performance optimizations
-
-
---
-
-
## 📈 Production Readiness Score
-
-
| Category | Score | Weight | Weighted Score |
-
|----------|-------|--------|----------------|
-
| Core Functionality | 8/10 | 20% | 1.6 |
-
| Type Safety | 9/10 | 15% | 1.35 |
-
| Error Handling | 8/10 | 15% | 1.2 |
-
| Connection Management | 7/10 | 15% | 1.05 |
-
| Advanced Features | 5/10 | 20% | 1.0 |
-
| Testing & Docs | 7/10 | 10% | 0.7 |
-
| Production Features | 5/10 | 5% | 0.25 |
-
-
**Overall Score: 7.15/10** (Production Ready for Most Use Cases)
-
-
**Mongoose Equivalent Score: ~8.5/10**
-
-
---
-
-
## 🚀 Conclusion
-
-
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.
-
-
**Estimated effort to reach production parity:** 3-6 months of full-time development
-
-
**Recommendation:** Use Mongoose for production, or invest heavily in Nozzle development before considering it as a replacement.
-
-
---
-
-
## 📝 Specific Code Issues Found
-
-
1. **model.ts:28** - Generic error messages, no structured error types
-
2. **model.ts:24-26** - Async validation explicitly unsupported (throws error)
-
3. **model.ts:71, 78, 118** - Unsafe type casting (`as OptionalUnlessRequiredId`)
-
4. ✅ **FIXED** - **model.ts:95-109** - Update operations now validate input via `parsePartial`
-
5. ✅ **FIXED** - All update methods (`update`, `updateOne`, `replaceOne`) now validate consistently
-
+6. ✅ **COMPLETED** - **client.ts** - Connection pooling and retry logic now fully exposed via `ConnectOptions`
-
7. ⚠️ **client.ts** - No way to manually reconnect if connection is lost (automatic retry handles most cases)
-
8. **client.ts** - Singleton pattern prevents multiple database connections
-
9. **No transaction support** - Critical for data consistency
-
10. **No query sanitization** - Direct MongoDB query passthrough (potential NoSQL injection)
-
11. ✅ **FIXED** - Removed `InsertType` in favor of Zod's native `z.input<T>` which handles defaults generically
-
12. **No error recovery** - Application will crash on connection loss
-
-
## 🆕 Recent Improvements
-
-
1. ✅ **Transaction Support Implemented** (client.ts, model.ts)
-
- `withTransaction()` helper for automatic transaction management
-
- `startSession()` and `endSession()` for manual control
-
- All Model methods accept session options
-
- Automatic commit/abort handling
-
- Support for TransactionOptions
-
- Clean API matching MongoDB best practices
-
- Comprehensive documentation with examples
-
- Works with MongoDB 4.0+ replica sets and 4.2+ sharded clusters
-
-
2. ✅ **Structured Error Handling Implemented** (errors.ts)
-
- Custom error class hierarchy with `NozzleError` base class
-
- `ValidationError` with Zod issue integration and field grouping
-
- `ConnectionError` with URI context
-
- `ConfigurationError`, `DocumentNotFoundError`, `OperationError`
-
- Operation-specific validation errors (insert/update/replace)
-
- `getFieldErrors()` method for field-specific error handling
-
- Comprehensive test coverage (errors_test.ts - 10 tests)
-
- Improved error messages with context
-
-
2. ✅ **Connection Retry Logic Implemented** (client.ts)
-
- Automatic retry for reads and writes via `retryReads` and `retryWrites`
-
- Full MongoDB driver connection options exposed
-
- Production-ready resilience configuration
-
- Comprehensive test coverage (connection_test.ts)
-
-
3. ✅ **Health Check Functionality Added** (client.ts)
-
- `healthCheck()` function for connection monitoring
-
- Response time measurement
-
- Detailed health status reporting
-
- Test coverage included
-
-
4. ✅ **Connection Pooling Exposed** (client.ts)
-
- Connection pooling options now available via `MongoClientOptions`
-
- Users can configure all MongoDB driver connection options
-
- Comprehensive test coverage (connection_test.ts)
-
-
5. ✅ **Update Validation Implemented** (model.ts:33-57, 95-109)
-
- `parsePartial` function validates partial update data
-
- Both `update` and `updateOne` methods now validate
-
- Comprehensive test coverage added
-
-
6. ✅ **Pagination Support Added** (model.ts:138-149)
-
- `findPaginated` method with skip, limit, and sort options
-
- Convenient helper for common pagination needs
-
-
7. ✅ **Index Management Implemented** (model.ts:147-250)
-
- Full index management API: createIndex, createIndexes, dropIndex, dropIndexes
-
- Index querying: listIndexes, getIndex, indexExists
-
- Index synchronization: syncIndexes for migrations
-
- Support for all MongoDB index types (unique, compound, text, geospatial)
-
- Comprehensive test coverage (index_test.ts)
-
-
8. ✅ **Enhanced Test Coverage**
-
- CRUD operations testing
-
- Update validation testing
-
- Default values testing
-
- Index management testing
-
- Connection retry and resilience testing
-
- Health check testing
-
- Error handling testing (10 comprehensive tests)
-
-
---
-
-
*Assessment Date: 2024*
-
*Last Updated: 2024*
-
*Assessed by: AI Code Review*
-
*Version: 0.2.0*
-
-
## 📋 Changelog
-
-
### Version 0.5.0 (Latest)
-
- ✅ **TRANSACTIONS IMPLEMENTED** - Full transaction support
-
- ✅ `withTransaction()` helper for automatic transaction management
-
- ✅ All Model methods accept session options
-
- ✅ Automatic commit/abort handling
-
- ✅ Phase 1 Critical Features: **ALL COMPLETED** 🎉
-
- Updated scores (7.15/10, up from 6.55/10)
-
- Advanced Features upgraded from 2/10 to 5/10
-
- **Production Ready** status achieved for most use cases
-
-
### Version 0.4.0
-
- ✅ Structured error handling implemented (custom error classes)
-
- ✅ `ValidationError` with field-specific error grouping
-
- ✅ `ConnectionError`, `ConfigurationError`, and other error types
-
- ✅ Operation context in validation errors (insert/update/replace)
-
- ✅ 10 comprehensive error handling tests added
-
- Updated scores (6.55/10, up from 5.85/10)
-
- Error Handling upgraded from 4/10 to 8/10
-
- Testing & Docs upgraded from 6/10 to 7/10
-
-
### Version 0.3.0
-
- ✅ Connection retry logic implemented (`retryReads`, `retryWrites`)
-
- ✅ Health check functionality added (`healthCheck()`)
-
- ✅ Full production resilience configuration support
-
- Updated scores (5.85/10, up from 5.1/10)
-
- Connection Management upgraded from 3/10 to 7/10
-
- Production Features upgraded from 2/10 to 5/10
-
-
### Version 0.2.0
-
- ✅ Update validation now implemented
-
- ✅ Pagination support added (`findPaginated`)
-
- ✅ Index management implemented
-
- ✅ Connection pooling options exposed
-
- ✅ Enhanced test coverage
-
- Updated scores and feature matrix
-
- Fixed incorrect code issue reports
-
-
### Version 0.1.0 (Initial)
-
- Initial production readiness assessment
+1 -1
README.md
···
- [x] Index management
- [ ] Middleware/hooks system
- [ ] Relationship/population support
-
- [ ] Better default value handling
+
- [x] Better default value handling
- [ ] Comprehensive edge case testing
### 🟢 Nice to Have
+60
model/core.ts
···
FindOptions,
UpdateOptions,
ReplaceOptions,
+
FindOneAndUpdateOptions,
+
FindOneAndReplaceOptions,
DeleteOptions,
CountDocumentsOptions,
AggregateOptions,
···
WithId,
BulkWriteOptions,
UpdateFilter,
+
ModifyResult,
} from "mongodb";
import { ObjectId } from "mongodb";
import type { Schema, Infer, Input } from "../types.ts";
···
query,
withoutId as Infer<T>,
options
+
);
+
}
+
+
/**
+
* Find a single document and update it
+
*
+
* Case handling:
+
* - If upsert: false (or undefined) → Normal update
+
* - If upsert: true → Defaults added to $setOnInsert for new document creation
+
*/
+
export async function findOneAndUpdate<T extends Schema>(
+
collection: Collection<Infer<T>>,
+
schema: T,
+
query: Filter<Infer<T>>,
+
data: Partial<z.infer<T>>,
+
options?: FindOneAndUpdateOptions
+
): Promise<ModifyResult<Infer<T>>> {
+
const validatedData = parsePartial(schema, data);
+
let updateDoc: UpdateFilter<Infer<T>> = { $set: validatedData as Partial<Infer<T>> };
+
+
if (options?.upsert) {
+
updateDoc = applyDefaultsForUpsert(schema, query, updateDoc);
+
}
+
+
const resolvedOptions: FindOneAndUpdateOptions & { includeResultMetadata: true } = {
+
...(options ?? {}),
+
includeResultMetadata: true as const,
+
};
+
+
return await collection.findOneAndUpdate(query, updateDoc, resolvedOptions);
+
}
+
+
/**
+
* Find a single document and replace it
+
*
+
* Defaults are applied via parseReplace(), which fills in missing fields
+
* for both normal replacements and upsert-created documents.
+
*/
+
export async function findOneAndReplace<T extends Schema>(
+
collection: Collection<Infer<T>>,
+
schema: T,
+
query: Filter<Infer<T>>,
+
data: Input<T>,
+
options?: FindOneAndReplaceOptions
+
): Promise<ModifyResult<Infer<T>>> {
+
const validatedData = parseReplace(schema, data);
+
const { _id, ...withoutId } = validatedData as Infer<T> & { _id?: unknown };
+
+
const resolvedOptions: FindOneAndReplaceOptions & { includeResultMetadata: true } = {
+
...(options ?? {}),
+
includeResultMetadata: true as const,
+
};
+
+
return await collection.findOneAndReplace(
+
query,
+
withoutId as Infer<T>,
+
resolvedOptions
);
}
+35
model/index.ts
···
FindOptions,
UpdateOptions,
ReplaceOptions,
+
FindOneAndUpdateOptions,
+
FindOneAndReplaceOptions,
DeleteOptions,
CountDocumentsOptions,
AggregateOptions,
···
UpdateResult,
WithId,
BulkWriteOptions,
+
ModifyResult,
} from "mongodb";
import type { ObjectId } from "mongodb";
import { getDb } from "../client/connection.ts";
···
}
/**
+
* Find a single document and update it
+
*
+
* @param query - MongoDB query filter
+
* @param data - Partial data to update
+
* @param options - FindOneAndUpdate options (including upsert and returnDocument)
+
* @returns Modify result containing the matched document
+
*/
+
async findOneAndUpdate(
+
query: Filter<Infer<T>>,
+
data: Partial<z.infer<T>>,
+
options?: FindOneAndUpdateOptions
+
): Promise<ModifyResult<Infer<T>>> {
+
return await core.findOneAndUpdate(this.collection, this.schema, query, data, options);
+
}
+
+
/**
* Replace a single document matching the query
*
* @param query - MongoDB query filter
···
options?: ReplaceOptions
): Promise<UpdateResult<Infer<T>>> {
return await core.replaceOne(this.collection, this.schema, query, data, options);
+
}
+
+
/**
+
* Find a single document and replace it
+
*
+
* @param query - MongoDB query filter
+
* @param data - Complete document data for replacement
+
* @param options - FindOneAndReplace options (including upsert and returnDocument)
+
* @returns Modify result containing the matched document
+
*/
+
async findOneAndReplace(
+
query: Filter<Infer<T>>,
+
data: Input<T>,
+
options?: FindOneAndReplaceOptions
+
): Promise<ModifyResult<Infer<T>>> {
+
return await core.findOneAndReplace(this.collection, this.schema, query, data, options);
}
/**
+51 -4
tests/defaults_test.ts
···
import { assertEquals, assertExists } from "@std/assert";
import { z } from "@zod/zod";
-
import { connect, disconnect, Model, type Input } from "../mod.ts";
+
import { connect, disconnect, Model } from "../mod.ts";
import { applyDefaultsForUpsert } from "../model/validation.ts";
import { MongoMemoryServer } from "mongodb-memory-server-core";
···
createdAt: z.date().default(() => new Date("2024-01-01T00:00:00Z")),
tags: z.array(z.string()).default([]),
});
-
-
type Product = z.infer<typeof productSchema>;
-
type ProductInsert = Input<typeof productSchema>;
let ProductModel: Model<typeof productSchema>;
let mongoServer: MongoMemoryServer;
···
sanitizeResources: false,
sanitizeOps: false,
});
+
+
Deno.test({
+
name: "Defaults: findOneAndUpdate with upsert preserves query equality fields",
+
async fn() {
+
await ProductModel.findOneAndUpdate(
+
{ name: "FindOneUpsert", category: "special" },
+
{ price: 12.5 },
+
{ upsert: true }
+
);
+
+
const product = await ProductModel.findOne({ name: "FindOneUpsert" });
+
assertExists(product);
+
+
assertEquals(product.category, "special"); // from query, not default
+
assertEquals(product.price, 12.5); // from update
+
assertEquals(product.inStock, true); // default applied
+
assertExists(product.createdAt); // default applied
+
assertEquals(product.tags, []); // default applied
+
},
+
sanitizeResources: false,
+
sanitizeOps: false,
+
});
+
+
Deno.test({
+
name: "Defaults: findOneAndReplace with upsert applies defaults on creation",
+
async fn() {
+
const result = await ProductModel.findOneAndReplace(
+
{ name: "FindOneReplaceUpsert" },
+
{
+
name: "FindOneReplaceUpsert",
+
price: 77.0,
+
},
+
{ upsert: true }
+
);
+
+
assertExists(result.lastErrorObject?.upserted);
+
+
const product = await ProductModel.findOne({ name: "FindOneReplaceUpsert" });
+
assertExists(product);
+
+
assertEquals(product.name, "FindOneReplaceUpsert");
+
assertEquals(product.price, 77.0);
+
assertEquals(product.category, "general"); // default applied
+
assertEquals(product.inStock, true); // default applied
+
assertExists(product.createdAt); // default applied
+
assertEquals(product.tags, []); // default applied
+
},
+
sanitizeResources: false,
+
sanitizeOps: false,
+
});
+1 -1
tests/errors_test.ts
···
// Test schemas
const userSchema = z.object({
name: z.string().min(1),
-
email: z.string().email(),
+
email: z.email(),
age: z.number().int().positive().optional(),
});