@@ -874,6 +874,18 @@ export const composeHandler = ({
874874
875875 fnLiteral += '\ntry{'
876876
877+ // When hooks access request.body/arrayBuffer, preserve raw body before parsing
878+ // Clone request first so hooks can still read from it
879+ // Skip if custom parse hooks exist - they'll read the request themselves
880+ // Note: FormData requests cannot be cloned for reuse - derive/resolve cannot
881+ // re-read the body for multipart/form-data requests (protocol limitation)
882+ if ( inference . request && ! hooks . parse ?. length ) {
883+ fnLiteral +=
884+ `const _ct=c.request.headers.get('content-type')\n` +
885+ `if(!_ct||!_ct.includes('multipart/form-data')){` +
886+ `c.rawBody=await c.request.clone().arrayBuffer()}\n`
887+ }
888+
877889 let parser : string | undefined =
878890 typeof hooks . parse === 'string'
879891 ? hooks . parse
@@ -904,55 +916,108 @@ export const composeHandler = ({
904916
905917 const isOptionalBody = ! ! validator . body ?. isOptional
906918
907- switch ( parser ) {
908- case 'json' :
909- case 'application/json' :
910- fnLiteral += adapter . parser . json ( isOptionalBody )
911- break
919+ // When inference.request is true and rawBody exists, parse from it
920+ // For FormData (no rawBody), use standard parser
921+ if ( inference . request ) {
922+ switch ( parser ) {
923+ case 'json' :
924+ case 'application/json' :
925+ if ( isOptionalBody )
926+ fnLiteral += 'if(c.rawBody){try{c.body=JSON.parse(new TextDecoder().decode(c.rawBody))}catch{}}else{try{c.body=await c.request.json()}catch{}}\n'
927+ else
928+ fnLiteral += 'c.body=c.rawBody?JSON.parse(new TextDecoder().decode(c.rawBody)):await c.request.json()\n'
929+ break
930+
931+ case 'text' :
932+ case 'text/plain' :
933+ fnLiteral += 'c.body=c.rawBody?new TextDecoder().decode(c.rawBody):await c.request.text()\n'
934+ break
935+
936+ case 'urlencoded' :
937+ case 'application/x-www-form-urlencoded' :
938+ fnLiteral += 'c.body=c.rawBody?parseQuery(new TextDecoder().decode(c.rawBody)):parseQuery(await c.request.text())\n'
939+ break
940+
941+ case 'arrayBuffer' :
942+ case 'application/octet-stream' :
943+ fnLiteral += 'c.body=c.rawBody??await c.request.arrayBuffer()\n'
944+ break
945+
946+ case 'formdata' :
947+ case 'multipart/form-data' :
948+ // FormData uses cloned request, standard parser works
949+ fnLiteral += adapter . parser . formData ( isOptionalBody )
950+ break
951+
952+ default :
953+ // Custom parser - let it access rawBody via context
954+ if ( parser in app [ '~parser' ] ) {
955+ fnLiteral += hasHeaders
956+ ? `let contentType = c.headers['content-type']`
957+ : `let contentType = c.request.headers.get('content-type')`
912958
913- case 'text' :
914- case 'text/plain' :
915- fnLiteral += adapter . parser . text ( isOptionalBody )
959+ fnLiteral +=
960+ `\nif(contentType){` +
961+ `const index=contentType.indexOf(';')\n` +
962+ `if(index!==-1)contentType=contentType.substring(0,index)}\n` +
963+ `else{contentType=''}` +
964+ `c.contentType=contentType\n` +
965+ `let result=parser['${ parser } '](c, contentType)\n` +
966+ `if(result instanceof Promise)result=await result\n` +
967+ `if(result instanceof ElysiaCustomStatusResponse)throw result\n` +
968+ `if(result!==undefined)c.body=result\n` +
969+ 'delete c.contentType\n'
970+ }
971+ }
972+ } else {
973+ switch ( parser ) {
974+ case 'json' :
975+ case 'application/json' :
976+ fnLiteral += adapter . parser . json ( isOptionalBody )
977+ break
916978
917- break
979+ case 'text' :
980+ case 'text/plain' :
981+ fnLiteral += adapter . parser . text ( isOptionalBody )
918982
919- case 'urlencoded' :
920- case 'application/x-www-form-urlencoded' :
921- fnLiteral += adapter . parser . urlencoded ( isOptionalBody )
983+ break
922984
923- break
985+ case 'urlencoded' :
986+ case 'application/x-www-form-urlencoded' :
987+ fnLiteral += adapter . parser . urlencoded ( isOptionalBody )
924988
925- case 'arrayBuffer' :
926- case 'application/octet-stream' :
927- fnLiteral += adapter . parser . arrayBuffer ( isOptionalBody )
989+ break
928990
929- break
991+ case 'arrayBuffer' :
992+ case 'application/octet-stream' :
993+ fnLiteral += adapter . parser . arrayBuffer ( isOptionalBody )
930994
931- case 'formdata' :
932- case 'multipart/form-data' :
933- fnLiteral += adapter . parser . formData ( isOptionalBody )
934- break
995+ break
935996
936- default :
937- if ( ( parser [ 0 ] as string ) in app [ '~parser' ] ) {
938- fnLiteral += hasHeaders
939- ? `let contentType = c.headers['content-type']`
940- : `let contentType = c.request.headers.get('content-type')`
997+ case 'formdata' :
998+ case 'multipart/form-data' :
999+ fnLiteral += adapter . parser . formData ( isOptionalBody )
1000+ break
9411001
942- fnLiteral +=
943- `\nif(contentType){` +
944- `const index=contentType.indexOf(';')\n` +
945- `if(index!==-1)contentType=contentType.substring(0,index)}\n` +
946- `else{contentType=''}` +
947- `c.contentType=contentType\n` +
948- `let result=parser['${ parser } '](c, contentType)\n` +
949- `if(result instanceof Promise)result=await result\n` +
950- `if(result instanceof ElysiaCustomStatusResponse)throw result\n` +
951- `if(result!==undefined)c.body=result\n` +
952- 'delete c.contentType\n'
953- }
1002+ default :
1003+ if ( parser in app [ '~parser' ] ) {
1004+ fnLiteral += hasHeaders
1005+ ? `let contentType = c.headers['content-type']`
1006+ : `let contentType = c.request.headers.get('content-type')`
9541007
955- break
1008+ fnLiteral +=
1009+ `\nif(contentType){` +
1010+ `const index=contentType.indexOf(';')\n` +
1011+ `if(index!==-1)contentType=contentType.substring(0,index)}\n` +
1012+ `else{contentType=''}` +
1013+ `c.contentType=contentType\n` +
1014+ `let result=parser['${ parser } '](c, contentType)\n` +
1015+ `if(result instanceof Promise)result=await result\n` +
1016+ `if(result instanceof ElysiaCustomStatusResponse)throw result\n` +
1017+ `if(result!==undefined)c.body=result\n` +
1018+ 'delete c.contentType\n'
1019+ }
1020+ }
9561021 }
9571022
9581023 reporter . resolve ( )
@@ -977,31 +1042,62 @@ export const composeHandler = ({
9771042 hasDefaultParser = true
9781043 const isOptionalBody = ! ! validator . body ?. isOptional
9791044
980- fnLiteral +=
981- `if(contentType)` +
982- `switch(contentType.charCodeAt(12)){` +
983- `\ncase 106:` +
984- adapter . parser . json ( isOptionalBody ) +
985- 'break' +
986- `\n` +
987- `case 120:` +
988- adapter . parser . urlencoded ( isOptionalBody ) +
989- `break` +
990- `\n` +
991- `case 111:` +
992- adapter . parser . arrayBuffer ( isOptionalBody ) +
993- `break` +
994- `\n` +
995- `case 114:` +
996- adapter . parser . formData ( isOptionalBody ) +
997- `break` +
998- `\n` +
999- `default:` +
1000- `if(contentType.charCodeAt(0)===116){` +
1001- adapter . parser . text ( isOptionalBody ) +
1002- `}` +
1003- `break\n` +
1004- `}`
1045+ // When rawBody is preserved, parse from it; for FormData use cloned request
1046+ if ( inference . request ) {
1047+ fnLiteral +=
1048+ `if(contentType)` +
1049+ `switch(contentType.charCodeAt(12)){` +
1050+ `\ncase 106:` + // application/json
1051+ ( isOptionalBody
1052+ ? 'if(c.rawBody){try{c.body=JSON.parse(new TextDecoder().decode(c.rawBody))}catch{}}else{try{c.body=await c.request.json()}catch{}}\n'
1053+ : 'c.body=c.rawBody?JSON.parse(new TextDecoder().decode(c.rawBody)):await c.request.json()\n' ) +
1054+ 'break' +
1055+ `\n` +
1056+ `case 120:` + // application/x-www-form-urlencoded
1057+ 'c.body=c.rawBody?parseQuery(new TextDecoder().decode(c.rawBody)):parseQuery(await c.request.text())\n' +
1058+ `break` +
1059+ `\n` +
1060+ `case 111:` + // application/octet-stream
1061+ 'c.body=c.rawBody??await c.request.arrayBuffer()\n' +
1062+ `break` +
1063+ `\n` +
1064+ `case 114:` + // multipart/form-data
1065+ adapter . parser . formData ( isOptionalBody ) +
1066+ `break` +
1067+ `\n` +
1068+ `default:` +
1069+ `if(contentType.charCodeAt(0)===116){` + // text/plain
1070+ 'c.body=c.rawBody?new TextDecoder().decode(c.rawBody):await c.request.text()\n' +
1071+ `}` +
1072+ `break\n` +
1073+ `}`
1074+ } else {
1075+ fnLiteral +=
1076+ `if(contentType)` +
1077+ `switch(contentType.charCodeAt(12)){` +
1078+ `\ncase 106:` +
1079+ adapter . parser . json ( isOptionalBody ) +
1080+ 'break' +
1081+ `\n` +
1082+ `case 120:` +
1083+ adapter . parser . urlencoded ( isOptionalBody ) +
1084+ `break` +
1085+ `\n` +
1086+ `case 111:` +
1087+ adapter . parser . arrayBuffer ( isOptionalBody ) +
1088+ `break` +
1089+ `\n` +
1090+ `case 114:` +
1091+ adapter . parser . formData ( isOptionalBody ) +
1092+ `break` +
1093+ `\n` +
1094+ `default:` +
1095+ `if(contentType.charCodeAt(0)===116){` +
1096+ adapter . parser . text ( isOptionalBody ) +
1097+ `}` +
1098+ `break\n` +
1099+ `}`
1100+ }
10051101 }
10061102
10071103 const reporter = report ( 'parse' , {
0 commit comments