|  |  | @ -31,6 +31,44 @@ export function nullifyValuesInObjects<T>(aObjects: T[], keys: string[]): T[] { | 
			
		
	
		
			
				
					|  |  |  |   }); | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | // Helper: Custom fast clone (faster than structuredClone for our use case)
 | 
			
		
	
		
			
				
					|  |  |  | function fastClone(obj: any, seen = new WeakMap()): any { | 
			
		
	
		
			
				
					|  |  |  |   // Handle primitives and null
 | 
			
		
	
		
			
				
					|  |  |  |   if (obj === null || typeof obj !== 'object') { | 
			
		
	
		
			
				
					|  |  |  |     return obj; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // Don't clone Big.js instances
 | 
			
		
	
		
			
				
					|  |  |  |   if (obj instanceof Big) { | 
			
		
	
		
			
				
					|  |  |  |     return obj; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // Handle circular references
 | 
			
		
	
		
			
				
					|  |  |  |   if (seen.has(obj)) { | 
			
		
	
		
			
				
					|  |  |  |     return seen.get(obj); | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // Handle arrays
 | 
			
		
	
		
			
				
					|  |  |  |   if (isArray(obj)) { | 
			
		
	
		
			
				
					|  |  |  |     const arr: any[] = []; | 
			
		
	
		
			
				
					|  |  |  |     seen.set(obj, arr); | 
			
		
	
		
			
				
					|  |  |  |     for (let i = 0; i < obj.length; i++) { | 
			
		
	
		
			
				
					|  |  |  |       arr[i] = fastClone(obj[i], seen); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |     return arr; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // Handle objects
 | 
			
		
	
		
			
				
					|  |  |  |   const cloned: any = {}; | 
			
		
	
		
			
				
					|  |  |  |   seen.set(obj, cloned); | 
			
		
	
		
			
				
					|  |  |  |   const keys = Object.keys(obj); | 
			
		
	
		
			
				
					|  |  |  |   for (let i = 0; i < keys.length; i++) { | 
			
		
	
		
			
				
					|  |  |  |     const key = keys[i]; | 
			
		
	
		
			
				
					|  |  |  |     cloned[key] = fastClone(obj[key], seen); | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |   return cloned; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | export function redactAttributes({ | 
			
		
	
		
			
				
					|  |  |  |   isFirstRun = true, | 
			
		
	
		
			
				
					|  |  |  |   object, | 
			
		
	
	
		
			
				
					|  |  | @ -44,44 +82,75 @@ export function redactAttributes({ | 
			
		
	
		
			
				
					|  |  |  |     return object; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // Create deep clone
 | 
			
		
	
		
			
				
					|  |  |  |   const redactedObject = isFirstRun | 
			
		
	
		
			
				
					|  |  |  |     ? JSON.parse(JSON.stringify(object)) | 
			
		
	
		
			
				
					|  |  |  |     : object; | 
			
		
	
		
			
				
					|  |  |  |   // Optimization 1: Use custom fast clone instead of structuredClone
 | 
			
		
	
		
			
				
					|  |  |  |   const redactedObject = isFirstRun ? fastClone(object) : object; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   for (const option of options) { | 
			
		
	
		
			
				
					|  |  |  |     if (redactedObject.hasOwnProperty(option.attribute)) { | 
			
		
	
		
			
				
					|  |  |  |       if (option.valueMap['*'] || option.valueMap['*'] === null) { | 
			
		
	
		
			
				
					|  |  |  |         redactedObject[option.attribute] = option.valueMap['*']; | 
			
		
	
		
			
				
					|  |  |  |       } else if (option.valueMap[redactedObject[option.attribute]]) { | 
			
		
	
		
			
				
					|  |  |  |         redactedObject[option.attribute] = | 
			
		
	
		
			
				
					|  |  |  |           option.valueMap[redactedObject[option.attribute]]; | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |   // Optimization 2: Pre-process options and separate wildcards from conditional mappings
 | 
			
		
	
		
			
				
					|  |  |  |   const wildcardAttrs = new Map<string, any>(); | 
			
		
	
		
			
				
					|  |  |  |   const conditionalAttrs = new Map<string, { [key: string]: any }>(); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   for (const opt of options) { | 
			
		
	
		
			
				
					|  |  |  |     if ('*' in opt.valueMap) { | 
			
		
	
		
			
				
					|  |  |  |       wildcardAttrs.set(opt.attribute, opt.valueMap['*']); | 
			
		
	
		
			
				
					|  |  |  |     } else { | 
			
		
	
		
			
				
					|  |  |  |       // If the attribute is not present on the current object,
 | 
			
		
	
		
			
				
					|  |  |  |       // check if it exists on any nested objects
 | 
			
		
	
		
			
				
					|  |  |  |       for (const property in redactedObject) { | 
			
		
	
		
			
				
					|  |  |  |         if (isArray(redactedObject[property])) { | 
			
		
	
		
			
				
					|  |  |  |           redactedObject[property] = redactedObject[property].map( | 
			
		
	
		
			
				
					|  |  |  |             (currentObject) => { | 
			
		
	
		
			
				
					|  |  |  |               return redactAttributes({ | 
			
		
	
		
			
				
					|  |  |  |                 options, | 
			
		
	
		
			
				
					|  |  |  |                 isFirstRun: false, | 
			
		
	
		
			
				
					|  |  |  |                 object: currentObject | 
			
		
	
		
			
				
					|  |  |  |               }); | 
			
		
	
		
			
				
					|  |  |  |       conditionalAttrs.set(opt.attribute, opt.valueMap); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // Optimization 3: Use iterative traversal with pointer-based queue
 | 
			
		
	
		
			
				
					|  |  |  |   const workQueue: any[] = [redactedObject]; | 
			
		
	
		
			
				
					|  |  |  |   let queueIndex = 0; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   while (queueIndex < workQueue.length) { | 
			
		
	
		
			
				
					|  |  |  |     const current = workQueue[queueIndex++]; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // Skip null/undefined
 | 
			
		
	
		
			
				
					|  |  |  |     if (current == null) { | 
			
		
	
		
			
				
					|  |  |  |       continue; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // Process wildcard attributes first (most common case)
 | 
			
		
	
		
			
				
					|  |  |  |     for (const [attribute, replacementValue] of wildcardAttrs) { | 
			
		
	
		
			
				
					|  |  |  |       if (attribute in current) { | 
			
		
	
		
			
				
					|  |  |  |         current[attribute] = replacementValue; | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // Process conditional attributes
 | 
			
		
	
		
			
				
					|  |  |  |     for (const [attribute, valueMap] of conditionalAttrs) { | 
			
		
	
		
			
				
					|  |  |  |       if (attribute in current) { | 
			
		
	
		
			
				
					|  |  |  |         const currentValue = current[attribute]; | 
			
		
	
		
			
				
					|  |  |  |         if (currentValue in valueMap) { | 
			
		
	
		
			
				
					|  |  |  |           current[attribute] = valueMap[currentValue]; | 
			
		
	
		
			
				
					|  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // Optimization 4: Use Object.keys() instead of for...in (faster, no inherited props)
 | 
			
		
	
		
			
				
					|  |  |  |     const keys = Object.keys(current); | 
			
		
	
		
			
				
					|  |  |  |     for (let i = 0; i < keys.length; i++) { | 
			
		
	
		
			
				
					|  |  |  |       const key = keys[i]; | 
			
		
	
		
			
				
					|  |  |  |       const value = current[key]; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |       // Optimization 5: Cache type check
 | 
			
		
	
		
			
				
					|  |  |  |       const valueType = typeof value; | 
			
		
	
		
			
				
					|  |  |  |       if (valueType !== 'object' || value === null) { | 
			
		
	
		
			
				
					|  |  |  |         continue; | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |       if (isArray(value)) { | 
			
		
	
		
			
				
					|  |  |  |         // Optimization 6: Batch array processing
 | 
			
		
	
		
			
				
					|  |  |  |         if (value.length > 0) { | 
			
		
	
		
			
				
					|  |  |  |           for (let j = 0; j < value.length; j++) { | 
			
		
	
		
			
				
					|  |  |  |             const item = value[j]; | 
			
		
	
		
			
				
					|  |  |  |             if (item != null && typeof item === 'object') { | 
			
		
	
		
			
				
					|  |  |  |               workQueue.push(item); | 
			
		
	
		
			
				
					|  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |           } | 
			
		
	
		
			
				
					|  |  |  |           ); | 
			
		
	
		
			
				
					|  |  |  |         } else if ( | 
			
		
	
		
			
				
					|  |  |  |           isObject(redactedObject[property]) && | 
			
		
	
		
			
				
					|  |  |  |           !(redactedObject[property] instanceof Big) | 
			
		
	
		
			
				
					|  |  |  |         ) { | 
			
		
	
		
			
				
					|  |  |  |           // Recursively call the function on the nested object
 | 
			
		
	
		
			
				
					|  |  |  |           redactedObject[property] = redactAttributes({ | 
			
		
	
		
			
				
					|  |  |  |             options, | 
			
		
	
		
			
				
					|  |  |  |             isFirstRun: false, | 
			
		
	
		
			
				
					|  |  |  |             object: redactedObject[property] | 
			
		
	
		
			
				
					|  |  |  |           }); | 
			
		
	
		
			
				
					|  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |       } else if (!(value instanceof Big)) { | 
			
		
	
		
			
				
					|  |  |  |         // Push object to queue (Big.js instances excluded)
 | 
			
		
	
		
			
				
					|  |  |  |         workQueue.push(value); | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
	
		
			
				
					|  |  | 
 |