1- /**
2- * Type guard to check if an object is a UserFile
3- */
1+ const MAX_STRING_LENGTH = 15000
2+ const MAX_DEPTH = 50
3+
4+ function truncateString ( value : string , maxLength = MAX_STRING_LENGTH ) : string {
5+ if ( value . length <= maxLength ) {
6+ return value
7+ }
8+ return `${ value . substring ( 0 , maxLength ) } ... [truncated ${ value . length - maxLength } chars]`
9+ }
10+
411export function isUserFile ( candidate : unknown ) : candidate is {
512 id : string
613 name : string
@@ -23,11 +30,6 @@ export function isUserFile(candidate: unknown): candidate is {
2330 )
2431}
2532
26- /**
27- * Filter function that transforms UserFile objects for display
28- * Removes internal fields: key, context
29- * Keeps user-friendly fields: id, name, url, size, type
30- */
3133function filterUserFile ( data : any ) : any {
3234 if ( isUserFile ( data ) ) {
3335 const { id, name, url, size, type } = data
@@ -36,50 +38,152 @@ function filterUserFile(data: any): any {
3638 return data
3739}
3840
39- /**
40- * Registry of filter functions to apply to data for cleaner display in logs/console.
41- * Add new filter functions here to handle additional data types.
42- */
43- const DISPLAY_FILTERS = [
44- filterUserFile ,
45- // Add more filters here as needed
46- ]
47-
48- /**
49- * Generic helper to filter internal/technical fields from data for cleaner display in logs and console.
50- * Applies all registered filters recursively to the data structure.
51- *
52- * To add a new filter:
53- * 1. Create a filter function that checks and transforms a specific data type
54- * 2. Add it to the DISPLAY_FILTERS array above
55- *
56- * @param data - Data to filter (objects, arrays, primitives)
57- * @returns Filtered data with internal fields removed
58- */
41+ const DISPLAY_FILTERS = [ filterUserFile ]
42+
5943export function filterForDisplay ( data : any ) : any {
60- if ( ! data || typeof data !== 'object' ) {
61- return data
62- }
44+ const seen = new WeakSet ( )
45+ return filterForDisplayInternal ( data , seen , 0 )
46+ }
6347
64- // Apply all registered filters
65- const filtered = data
66- for ( const filterFn of DISPLAY_FILTERS ) {
67- const result = filterFn ( filtered )
68- if ( result !== filtered ) {
69- // Filter matched and transformed the data
70- return result
48+ function getObjectType ( data : unknown ) : string {
49+ return Object . prototype . toString . call ( data ) . slice ( 8 , - 1 )
50+ }
51+
52+ function filterForDisplayInternal ( data : any , seen : WeakSet < object > , depth : number ) : any {
53+ try {
54+ if ( data === null || data === undefined ) {
55+ return data
7156 }
72- }
7357
74- // No filters matched - recursively filter nested structures
75- if ( Array . isArray ( filtered ) ) {
76- return filtered . map ( filterForDisplay )
77- }
58+ const dataType = typeof data
59+
60+ if ( dataType === 'string' ) {
61+ // Remove null bytes which are not allowed in PostgreSQL JSONB
62+ const sanitized = data . includes ( '\u0000' ) ? data . replace ( / \u0000 / g, '' ) : data
63+ return truncateString ( sanitized )
64+ }
65+
66+ if ( dataType === 'number' ) {
67+ if ( Number . isNaN ( data ) ) {
68+ return '[NaN]'
69+ }
70+ if ( ! Number . isFinite ( data ) ) {
71+ return data > 0 ? '[Infinity]' : '[-Infinity]'
72+ }
73+ return data
74+ }
75+
76+ if ( dataType === 'boolean' ) {
77+ return data
78+ }
79+
80+ if ( dataType === 'bigint' ) {
81+ return `[BigInt: ${ data . toString ( ) } ]`
82+ }
83+
84+ if ( dataType === 'symbol' ) {
85+ return `[Symbol: ${ data . toString ( ) } ]`
86+ }
87+
88+ if ( dataType === 'function' ) {
89+ return `[Function: ${ data . name || 'anonymous' } ]`
90+ }
91+
92+ if ( dataType !== 'object' ) {
93+ return '[Unknown Type]'
94+ }
7895
79- // Recursively filter object properties
80- const result : any = { }
81- for ( const [ key , value ] of Object . entries ( filtered ) ) {
82- result [ key ] = filterForDisplay ( value )
96+ if ( seen . has ( data ) ) {
97+ return '[Circular Reference]'
98+ }
99+
100+ if ( depth > MAX_DEPTH ) {
101+ return '[Max Depth Exceeded]'
102+ }
103+
104+ const objectType = getObjectType ( data )
105+
106+ switch ( objectType ) {
107+ case 'Date' : {
108+ const timestamp = ( data as Date ) . getTime ( )
109+ if ( Number . isNaN ( timestamp ) ) {
110+ return '[Invalid Date]'
111+ }
112+ return ( data as Date ) . toISOString ( )
113+ }
114+
115+ case 'RegExp' :
116+ return ( data as RegExp ) . toString ( )
117+
118+ case 'URL' :
119+ return ( data as URL ) . toString ( )
120+
121+ case 'Error' : {
122+ const err = data as Error
123+ return {
124+ name : err . name ,
125+ message : truncateString ( err . message ) ,
126+ stack : err . stack ? truncateString ( err . stack ) : undefined ,
127+ }
128+ }
129+
130+ case 'ArrayBuffer' :
131+ return `[ArrayBuffer: ${ ( data as ArrayBuffer ) . byteLength } bytes]`
132+
133+ case 'Map' : {
134+ const obj : Record < string , any > = { }
135+ for ( const [ key , value ] of ( data as Map < any , any > ) . entries ( ) ) {
136+ const keyStr = typeof key === 'string' ? key : String ( key )
137+ obj [ keyStr ] = filterForDisplayInternal ( value , seen , depth + 1 )
138+ }
139+ return obj
140+ }
141+
142+ case 'Set' :
143+ return Array . from ( data as Set < any > ) . map ( ( item ) =>
144+ filterForDisplayInternal ( item , seen , depth + 1 )
145+ )
146+
147+ case 'WeakMap' :
148+ return '[WeakMap]'
149+
150+ case 'WeakSet' :
151+ return '[WeakSet]'
152+
153+ case 'WeakRef' :
154+ return '[WeakRef]'
155+
156+ case 'Promise' :
157+ return '[Promise]'
158+ }
159+
160+ if ( ArrayBuffer . isView ( data ) ) {
161+ return `[${ objectType } : ${ ( data as ArrayBufferView ) . byteLength } bytes]`
162+ }
163+
164+ seen . add ( data )
165+
166+ for ( const filterFn of DISPLAY_FILTERS ) {
167+ const result = filterFn ( data )
168+ if ( result !== data ) {
169+ return filterForDisplayInternal ( result , seen , depth + 1 )
170+ }
171+ }
172+
173+ if ( Array . isArray ( data ) ) {
174+ return data . map ( ( item ) => filterForDisplayInternal ( item , seen , depth + 1 ) )
175+ }
176+
177+ const result : Record < string , any > = { }
178+ for ( const key of Object . keys ( data ) ) {
179+ try {
180+ result [ key ] = filterForDisplayInternal ( data [ key ] , seen , depth + 1 )
181+ } catch {
182+ result [ key ] = '[Error accessing property]'
183+ }
184+ }
185+ return result
186+ } catch {
187+ return '[Unserializable]'
83188 }
84- return result
85189}
0 commit comments