fix(classes.doc (convertFilterForMongoDb)): Improve filter conversion: handle logical operators, merge operator objects, add nested filter tests and docs, and fix test script
This commit is contained in:
		| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@push.rocks/smartdata', | ||||
|   version: '5.16.3', | ||||
|   version: '5.16.4', | ||||
|   description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.' | ||||
| } | ||||
|   | ||||
| @@ -199,21 +199,51 @@ export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => { | ||||
|     throw new Error('$where operator is not allowed for security reasons'); | ||||
|   } | ||||
|    | ||||
|   // Special case: detect MongoDB operators and pass them through directly | ||||
|   const topLevelOperators = ['$and', '$or', '$nor', '$not', '$text', '$regex']; | ||||
|   // Handle logical operators recursively | ||||
|   const logicalOperators = ['$and', '$or', '$nor', '$not']; | ||||
|   const processedFilter: { [key: string]: any } = {}; | ||||
|    | ||||
|   for (const key of Object.keys(filterArg)) { | ||||
|     if (topLevelOperators.includes(key)) { | ||||
|       return filterArg; // Return the filter as-is for MongoDB operators | ||||
|     if (logicalOperators.includes(key)) { | ||||
|       if (key === '$not') { | ||||
|         processedFilter[key] = convertFilterForMongoDb(filterArg[key]); | ||||
|       } else if (Array.isArray(filterArg[key])) { | ||||
|         processedFilter[key] = filterArg[key].map((subFilter: any) => convertFilterForMongoDb(subFilter)); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // If only logical operators, return them | ||||
|   const hasOnlyLogicalOperators = Object.keys(filterArg).every(key => logicalOperators.includes(key)); | ||||
|   if (hasOnlyLogicalOperators) { | ||||
|     return processedFilter; | ||||
|   } | ||||
|  | ||||
|   // Original conversion logic for non-MongoDB query objects | ||||
|   const convertedFilter: { [key: string]: any } = {}; | ||||
|    | ||||
|   // Helper to merge operator objects | ||||
|   const mergeIntoConverted = (path: string, value: any) => { | ||||
|     const existing = convertedFilter[path]; | ||||
|     if (!existing) { | ||||
|       convertedFilter[path] = value; | ||||
|     } else if ( | ||||
|       typeof existing === 'object' && !Array.isArray(existing) && | ||||
|       typeof value === 'object' && !Array.isArray(value) && | ||||
|       (Object.keys(existing).some(k => k.startsWith('$')) || Object.keys(value).some(k => k.startsWith('$'))) | ||||
|     ) { | ||||
|       // Both have operators, merge them | ||||
|       convertedFilter[path] = { ...existing, ...value }; | ||||
|     } else { | ||||
|       // Otherwise later wins | ||||
|       convertedFilter[path] = value; | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const convertFilterArgument = (keyPathArg2: string, filterArg2: any) => { | ||||
|     if (Array.isArray(filterArg2)) { | ||||
|       // Arrays are typically used as values for operators like $in or as direct equality matches | ||||
|       convertedFilter[keyPathArg2] = filterArg2; | ||||
|       mergeIntoConverted(keyPathArg2, filterArg2); | ||||
|       return; | ||||
|     } else if (typeof filterArg2 === 'object' && filterArg2 !== null) { | ||||
|       // Check if this is an object with MongoDB operators | ||||
| @@ -264,8 +294,8 @@ export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => { | ||||
|           throw new Error('$size operator requires a numeric value'); | ||||
|         } | ||||
|          | ||||
|         // Pass the operator object through | ||||
|         convertedFilter[keyPathArg2] = filterArg2; | ||||
|         // Use merge helper to handle duplicate paths | ||||
|         mergeIntoConverted(keyPathArg2, filterArg2); | ||||
|         return; | ||||
|       } | ||||
|        | ||||
| @@ -282,13 +312,20 @@ export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => { | ||||
|       } | ||||
|     } else { | ||||
|       // Primitive values | ||||
|       convertedFilter[keyPathArg2] = filterArg2; | ||||
|       mergeIntoConverted(keyPathArg2, filterArg2); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   for (const key of Object.keys(filterArg)) { | ||||
|     convertFilterArgument(key, filterArg[key]); | ||||
|     // Skip logical operators, they were already processed | ||||
|     if (!logicalOperators.includes(key)) { | ||||
|       convertFilterArgument(key, filterArg[key]); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Add back processed logical operators | ||||
|   Object.assign(convertedFilter, processedFilter); | ||||
|    | ||||
|   return convertedFilter; | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user