44 */
55const MAX_STRING_LENGTH = 10000
66
7+ /**
8+ * Maximum recursion depth to prevent stack overflow
9+ */
10+ const MAX_DEPTH = 50
11+
712/**
813 * Truncates a string if it exceeds the maximum length
914 */
@@ -66,6 +71,7 @@ const DISPLAY_FILTERS = [
6671 * Applies all registered filters recursively to the data structure.
6772 * Also truncates long strings to prevent database storage issues.
6873 *
74+ *
6975 * To add a new filter:
7076 * 1. Create a filter function that checks and transforms a specific data type
7177 * 2. Add it to the DISPLAY_FILTERS array above
@@ -74,6 +80,11 @@ const DISPLAY_FILTERS = [
7480 * @returns Filtered data with internal fields removed and long strings truncated
7581 */
7682export function filterForDisplay ( data : any ) : any {
83+ const seen = new WeakSet ( )
84+ return filterForDisplayInternal ( data , seen , 0 )
85+ }
86+
87+ function filterForDisplayInternal ( data : any , seen : WeakSet < object > , depth : number ) : any {
7788 // Handle null/undefined
7889 if ( data === null || data === undefined ) {
7990 return data
@@ -84,30 +95,78 @@ export function filterForDisplay(data: any): any {
8495 return truncateString ( data )
8596 }
8697
87- // Return primitives as-is
98+ // Return primitives as-is (number, boolean, bigint, symbol, function)
8899 if ( typeof data !== 'object' ) {
89100 return data
90101 }
91102
103+ // Prevent infinite recursion from circular references
104+ if ( seen . has ( data ) ) {
105+ return '[Circular Reference]'
106+ }
107+
108+ // Prevent stack overflow from very deep nesting
109+ if ( depth > MAX_DEPTH ) {
110+ return '[Max Depth Exceeded]'
111+ }
112+
113+ // Handle special object types before adding to seen set
114+ // Date objects - convert to ISO string
115+ if ( data instanceof Date ) {
116+ return data . toISOString ( )
117+ }
118+
119+ // Error objects - preserve message and stack
120+ if ( data instanceof Error ) {
121+ return {
122+ name : data . name ,
123+ message : truncateString ( data . message ) ,
124+ stack : data . stack ? truncateString ( data . stack ) : undefined ,
125+ }
126+ }
127+
128+ // Buffer or TypedArray - don't serialize full content
129+ if ( ArrayBuffer . isView ( data ) || data instanceof ArrayBuffer ) {
130+ return `[Binary Data: ${ data . byteLength } bytes]`
131+ }
132+
133+ // Map - convert to object
134+ if ( data instanceof Map ) {
135+ const obj : Record < string , any > = { }
136+ for ( const [ key , value ] of data . entries ( ) ) {
137+ const keyStr = typeof key === 'string' ? key : String ( key )
138+ obj [ keyStr ] = filterForDisplayInternal ( value , seen , depth + 1 )
139+ }
140+ return obj
141+ }
142+
143+ // Set - convert to array
144+ if ( data instanceof Set ) {
145+ return Array . from ( data ) . map ( ( item ) => filterForDisplayInternal ( item , seen , depth + 1 ) )
146+ }
147+
148+ // Track this object to detect circular references
149+ seen . add ( data )
150+
92151 // Apply all registered filters
93- const filtered = data
94152 for ( const filterFn of DISPLAY_FILTERS ) {
95- const result = filterFn ( filtered )
96- if ( result !== filtered ) {
153+ const result = filterFn ( data )
154+ if ( result !== data ) {
97155 // Filter matched and transformed the data
98- return result
156+ // Recursively filter the result in case it contains nested objects
157+ return filterForDisplayInternal ( result , seen , depth + 1 )
99158 }
100159 }
101160
102161 // No filters matched - recursively filter nested structures
103- if ( Array . isArray ( filtered ) ) {
104- return filtered . map ( filterForDisplay )
162+ if ( Array . isArray ( data ) ) {
163+ return data . map ( ( item ) => filterForDisplayInternal ( item , seen , depth + 1 ) )
105164 }
106165
107166 // Recursively filter object properties
108- const result : any = { }
109- for ( const [ key , value ] of Object . entries ( filtered ) ) {
110- result [ key ] = filterForDisplay ( value )
167+ const result : Record < string , any > = { }
168+ for ( const [ key , value ] of Object . entries ( data ) ) {
169+ result [ key ] = filterForDisplayInternal ( value , seen , depth + 1 )
111170 }
112171 return result
113172}
0 commit comments