@@ -87,30 +87,42 @@ export const GET = withRouteHandler(
8787
8888 logger . info ( 'Exporting markdown with embedded images' , { id, imageCount : imageIds . length } )
8989
90- const assetMap = new Map < string , { filename : string ; buffer : Buffer } > ( )
91- const usedFilenames = new Set < string > ( )
92-
93- await Promise . allSettled (
90+ // Fetch all images in parallel, then deduplicate filenames serially to avoid
91+ // a race where two concurrent callbacks both pass the "not yet seen" check.
92+ const fetchResults = await Promise . allSettled (
9493 imageIds . map ( async ( imageId ) => {
95- try {
96- const imgRecord = await getFileMetadataById ( imageId )
97- if ( ! imgRecord ) return
98- const imgHasAccess = await verifyFileAccess ( imgRecord . key , authResult . userId )
99- if ( ! imgHasAccess ) return
100- const imgBuffer = await downloadFile ( {
101- key : imgRecord . key ,
102- context : imgRecord . context as StorageContext ,
103- } )
104- const preferred = safeFilename ( imgRecord . originalName )
105- const filename = deduplicatedFilename ( preferred , usedFilenames , imageId )
106- usedFilenames . add ( filename )
107- assetMap . set ( imageId , { filename, buffer : imgBuffer } )
108- } catch ( err ) {
109- logger . warn ( 'Failed to fetch asset for export' , { imageId, error : toError ( err ) . message } )
110- }
94+ const imgRecord = await getFileMetadataById ( imageId )
95+ if ( ! imgRecord ) return null
96+ const imgHasAccess = await verifyFileAccess ( imgRecord . key , authResult . userId )
97+ if ( ! imgHasAccess ) return null
98+ const imgBuffer = await downloadFile ( {
99+ key : imgRecord . key ,
100+ context : imgRecord . context as StorageContext ,
101+ } )
102+ return { imageId, originalName : imgRecord . originalName , buffer : imgBuffer }
111103 } )
112104 )
113105
106+ const assetMap = new Map < string , { filename : string ; buffer : Buffer } > ( )
107+ const usedFilenames = new Set < string > ( )
108+
109+ for ( let i = 0 ; i < fetchResults . length ; i ++ ) {
110+ const result = fetchResults [ i ]
111+ if ( result . status === 'rejected' ) {
112+ logger . warn ( 'Failed to fetch asset for export' , {
113+ imageId : imageIds [ i ] ,
114+ error : toError ( result . reason ) . message ,
115+ } )
116+ continue
117+ }
118+ if ( ! result . value ) continue
119+ const { imageId, originalName, buffer } = result . value
120+ const preferred = safeFilename ( originalName )
121+ const filename = deduplicatedFilename ( preferred , usedFilenames , imageId )
122+ usedFilenames . add ( filename )
123+ assetMap . set ( imageId , { filename, buffer } )
124+ }
125+
114126 for ( const [ imageId , asset ] of assetMap ) {
115127 const escapedId = imageId . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
116128 mdContent = mdContent . replace (
0 commit comments