qmemory
Version:
A comprehensive production-ready Node.js utility library with MongoDB document operations, user ownership enforcement, Express.js HTTP utilities, environment-aware logging, and in-memory storage. Features 96%+ test coverage with comprehensive error handli
387 lines (358 loc) • 17.2 kB
JavaScript
/**
* Database Document Helper Utilities
* Generic MongoDB CRUD operations with consistent error handling and type safety
*
* This module provides a comprehensive set of generic document helpers that complement
* the existing user-owned document operations. These utilities centralize common CRUD
* patterns, eliminate duplicate try-catch blocks, and provide consistent error handling
* across storage operations.
*
* Design philosophy:
* - Generic operations: Work with any Mongoose model without user ownership constraints
* - Consistent error handling: Graceful handling of invalid ObjectIds and database errors
* - Type safety: TypeScript-style generics for better development experience
* - Defensive programming: Always return safe fallback values instead of throwing
* - Performance patterns: Efficient MongoDB operations with proper options
*
* Use cases:
* - Administrative operations that bypass user ownership
* - System-level document management and cleanup
* - Bulk operations for data migration and maintenance
* - Generic CRUD where user context is not required
*
* Integration with existing utilities:
* - Complements lib/document-ops.js (user-owned documents)
* - Uses lib/database-utils.js for error handling and safe operations
* - Follows lib/logging-utils.js patterns for consistent debugging
*/
const mongoose = require('mongoose'); // MongoDB object modeling for Node.js
const { safeDbOperation, handleMongoError } = require('./database-utils'); // Consistent error handling and safe operations
/**
* Generic helper for safe document retrieval by ID
*
* Handles invalid ObjectId errors and provides consistent error handling
* for document lookup operations. This function provides a safe wrapper
* around MongoDB's findById with graceful error handling.
*
* Error handling approach:
* - Invalid ObjectId: Returns undefined instead of throwing CastError
* - Database connection issues: Returns undefined with logging
* - Document not found: Returns undefined (null normalized)
* - All errors logged but operation continues gracefully
*
* @param {Object} model - Mongoose model to query
* @param {string} id - Document ID to find
* @returns {Promise<Object|undefined>} Document or undefined if not found/error
*/
async function findDocumentById(model, id) {
console.log(`findDocumentById is running with ${model.modelName} model and ${id} id`);
const result = await safeDbOperation(
async () => {
const document = await model.findById(id);
return document || undefined; // Normalize null to undefined for consistent return type
},
'findDocumentById',
{ model: model.modelName, id }
);
if (result.success) {
console.log(`findDocumentById is returning ${result.data ? 'document found' : 'undefined'}`);
return result.data;
} else {
console.log(`findDocumentById is returning undefined due to error`);
return undefined; // Handle invalid ObjectId or database connection errors gracefully
}
}
/**
* Generic helper for safe document update by ID
*
* Standardizes update operations with consistent error handling and new document return.
* This function provides a safe wrapper around MongoDB's findByIdAndUpdate with
* the 'new: true' option to return the updated document.
*
* Update strategy:
* - Uses findByIdAndUpdate with new: true for immediate updated document access
* - Handles validation errors gracefully by returning undefined
* - Logs all operations for debugging and monitoring
* - Maintains referential integrity with atomic operations
*
* @param {Object} model - Mongoose model to update
* @param {string} id - Document ID to update
* @param {Object} updates - Partial updates to apply
* @returns {Promise<Object|undefined>} Updated document or undefined if not found/error
*/
async function updateDocumentById(model, id, updates) {
console.log(`updateDocumentById is running with ${model.modelName} model, ${id} id, and updates`);
const result = await safeDbOperation(
async () => {
const document = await model.findByIdAndUpdate(id, updates, { new: true });
return document || undefined; // Normalize null to undefined for consistent return type
},
'updateDocumentById',
{ model: model.modelName, id, updateFields: Object.keys(updates) }
);
if (result.success) {
console.log(`updateDocumentById is returning ${result.data ? 'updated document' : 'undefined'}`);
return result.data;
} else {
console.log(`updateDocumentById is returning undefined due to error`);
return undefined; // Handle invalid ObjectId, validation errors, or database connection issues gracefully
}
}
/**
* Generic helper for safe document deletion by ID
*
* Provides consistent boolean return pattern for deletion operations.
* This function provides a safe wrapper around MongoDB's findByIdAndDelete
* with explicit boolean conversion for consistent return patterns.
*
* Deletion strategy:
* - Uses findByIdAndDelete for atomic operation
* - Returns explicit boolean for consistent API
* - Logs all operations for audit trails
* - Handles errors gracefully without throwing
*
* @param {Object} model - Mongoose model to delete from
* @param {string} id - Document ID to delete
* @returns {Promise<boolean>} True if deleted successfully, false otherwise
*/
async function deleteDocumentById(model, id) {
console.log(`deleteDocumentById is running with ${model.modelName} model and ${id} id`);
const result = await safeDbOperation(
async () => {
const deletedDocument = await model.findByIdAndDelete(id);
return !!deletedDocument; // Convert truthy/falsy to explicit boolean for consistent return type
},
'deleteDocumentById',
{ model: model.modelName, id }
);
if (result.success) {
console.log(`deleteDocumentById is returning ${result.data}`);
return result.data;
} else {
console.log(`deleteDocumentById is returning false due to error`);
return false; // Handle invalid ObjectId or database connection errors gracefully
}
}
/**
* Helper for cascading deletion operations
*
* Safely deletes related documents and handles cleanup operations.
* This function implements the cascade delete pattern commonly needed
* when removing documents that have related data in other collections.
*
* Cascade strategy:
* - Performs cascade operations first to maintain referential integrity
* - Continues with main deletion even if some cascade operations fail
* - Logs cascade failures but doesn't stop the main operation
* - Returns boolean based on main deletion success only
*
* Common use cases:
* - User deletion with profile, settings, and content cleanup
* - Order deletion with line items, payments, and shipping cleanup
* - Project deletion with files, permissions, and history cleanup
*
* @param {Object} mainModel - Primary model to delete from
* @param {string} mainId - ID of main document to delete
* @param {Array} cascadeOperations - Array of cleanup operations to perform
* @returns {Promise<boolean>} True if main deletion succeeded, regardless of cascade results
*/
async function cascadeDeleteDocument(mainModel, mainId, cascadeOperations = []) {
console.log(`cascadeDeleteDocument is running with ${mainModel.modelName} model, ${mainId} id, and ${cascadeOperations.length} cascade operations`);
const result = await safeDbOperation(
async () => {
// Perform cascade operations first to maintain referential integrity
for (let i = 0; i < cascadeOperations.length; i++) {
try {
await cascadeOperations[i](); // Execute each cleanup operation independently
console.log(`[DEBUG] Cascade operation ${i + 1} completed successfully`);
} catch (error) {
console.warn(`[WARN] Cascade operation ${i + 1} failed:`, error.message);
// Continue with other operations - cascade failures shouldn't stop main deletion
}
}
// Delete main document after cascade cleanup
const deletedDocument = await mainModel.findByIdAndDelete(mainId);
return !!deletedDocument; // Convert truthy/falsy to explicit boolean
},
'cascadeDeleteDocument',
{ model: mainModel.modelName, id: mainId, cascadeCount: cascadeOperations.length }
);
if (result.success) {
console.log(`cascadeDeleteDocument is returning ${result.data}`);
return result.data;
} else {
console.log(`cascadeDeleteDocument is returning false due to error`);
return false; // Handle database errors gracefully
}
}
/**
* Generic helper for safe document creation
*
* Standardizes document creation with consistent error handling.
* Unlike other helpers that return undefined on error, this function
* throws errors to maintain the expected behavior for creation operations
* where the caller needs to handle validation and duplicate key errors.
*
* Creation strategy:
* - Uses new model() + save() pattern for middleware hook execution
* - Allows validation errors and duplicate key errors to bubble up
* - Logs creation operations for audit trails
* - Integrates with existing error handling patterns
*
* @param {Object} model - Mongoose model to create document in
* @param {Object} data - Data to create document with
* @returns {Promise<Object>} Created document
* @throws {Error} If creation fails (validation, duplicate key, etc.)
*/
async function createDocument(model, data) {
console.log(`createDocument is running with ${model.modelName} model and data`);
const result = await safeDbOperation(
async () => {
const document = new model(data); // Create Mongoose document instance for validation and middleware hooks
return await document.save(); // Persist to MongoDB with schema validation and pre/post hooks
},
'createDocument',
{ model: model.modelName, dataFields: Object.keys(data) }
);
if (result.success) {
console.log(`createDocument is returning created document`);
return result.data;
} else {
console.log(`createDocument is throwing error due to failure`);
// Re-throw the original error for creation operations where caller needs to handle specific error types
const error = new Error(result.error.message);
error.code = result.error.statusCode;
error.type = result.error.type;
throw error;
}
}
/**
* Generic helper for safe document query with find condition
*
* Handles database errors gracefully and provides consistent return pattern.
* This function provides a safe wrapper around MongoDB's find operation
* with optional sorting and consistent error handling.
*
* Query strategy:
* - Uses MongoDB find() with flexible condition objects
* - Supports optional sorting for consistent result ordering
* - Returns empty array on any error for safe iteration
* - Logs query patterns for performance monitoring
*
* @param {Object} model - Mongoose model to query
* @param {Object} condition - Query condition object
* @param {Object} sortOptions - Optional sort configuration
* @returns {Promise<Array>} Array of documents or empty array on error
*/
async function findDocuments(model, condition, sortOptions = null) {
console.log(`findDocuments is running with ${model.modelName} model and condition`);
const result = await safeDbOperation(
async () => {
let query = model.find(condition); // MongoDB query builder with find condition for document filtering
if (sortOptions) {
query = query.sort(sortOptions); // Apply sorting if provided for consistent result ordering
}
return await query; // Execute query and return document array
},
'findDocuments',
{ model: model.modelName, condition, hasSort: !!sortOptions }
);
if (result.success) {
console.log(`findDocuments is returning ${result.data.length} documents`);
return result.data;
} else {
console.log(`findDocuments is returning empty array due to error`);
return []; // Handle database errors gracefully with empty array fallback
}
}
/**
* Generic helper for safe single document query with find condition
*
* Provides consistent undefined return for not found or error cases.
* This function provides a safe wrapper around MongoDB's findOne operation
* with consistent null-to-undefined normalization.
*
* Query strategy:
* - Uses MongoDB findOne() for single document retrieval
* - Normalizes null to undefined for consistent API
* - Handles database errors gracefully
* - Logs query patterns for debugging
*
* @param {Object} model - Mongoose model to query
* @param {Object} condition - Query condition object
* @returns {Promise<Object|undefined>} Document or undefined if not found/error
*/
async function findOneDocument(model, condition) {
console.log(`findOneDocument is running with ${model.modelName} model and condition`);
const result = await safeDbOperation(
async () => {
const document = await model.findOne(condition); // MongoDB findOne query for single document retrieval
return document || undefined; // Normalize null to undefined for consistent return type
},
'findOneDocument',
{ model: model.modelName, condition }
);
if (result.success) {
console.log(`findOneDocument is returning ${result.data ? 'document found' : 'undefined'}`);
return result.data;
} else {
console.log(`findOneDocument is returning undefined due to error`);
return undefined; // Handle database errors gracefully with undefined fallback
}
}
/**
* Helper for bulk document updates with consistent error handling
*
* Useful for operations like reordering where multiple documents need updates.
* This function provides efficient bulk update capabilities with individual
* error handling to prevent cascading failures.
*
* Bulk update strategy:
* - Processes updates sequentially to avoid overwhelming database
* - Continues processing even if individual updates fail
* - Returns count of successful updates for progress tracking
* - Logs bulk operation progress for monitoring
*
* Performance considerations:
* - Uses individual findByIdAndUpdate calls for simplicity and error isolation
* - Could be optimized with bulkWrite() for very large operations
* - Provides progress feedback through success counting
*
* @param {Object} model - Mongoose model to update
* @param {Array} updates - Array of update operations with id and data
* @returns {Promise<number>} Number of successful updates
*/
async function bulkUpdateDocuments(model, updates) {
console.log(`bulkUpdateDocuments is running with ${model.modelName} model and ${updates.length} updates`);
let successCount = 0; // Track successful updates for return value
for (let i = 0; i < updates.length; i++) {
const update = updates[i];
const result = await safeDbOperation(
async () => {
const updatedDocument = await model.findByIdAndUpdate(update.id, update.data);
return !!updatedDocument; // Return boolean for success tracking
},
'bulkUpdateDocuments_single',
{ model: model.modelName, updateIndex: i, id: update.id }
);
if (result.success && result.data) {
successCount++; // Increment counter only if document was found and updated
}
// Continue with other updates even if one fails to prevent cascading failures
}
console.log(`bulkUpdateDocuments is returning ${successCount} successful updates`);
return successCount;
}
// Export functions using object shorthand for clean module interface
// This pattern provides a consistent export structure across all utility modules
// Generic document helpers for MongoDB operations without user ownership constraints
module.exports = {
findDocumentById, // safe document retrieval by ID with error handling
updateDocumentById, // safe document update by ID with new document return
deleteDocumentById, // safe document deletion by ID with boolean return
cascadeDeleteDocument, // cascading deletion with cleanup operations
createDocument, // safe document creation with validation error propagation
findDocuments, // safe document query with find condition and sorting
findOneDocument, // safe single document query with consistent undefined return
bulkUpdateDocuments // bulk document updates with individual error handling
};