@@ -214,6 +214,71 @@ test('afterResponse hook can return the provided response', async t => {
214214 t . true ( originalResponse ?. bodyUsed ) ;
215215} ) ;
216216
217+ test ( 'afterResponse hook can wrap the provided body in a new response' , async t => {
218+ const responseText = await ky ( 'https://example.com' , {
219+ fetch : async ( ) => new Response ( 'ok' , {
220+ headers : {
221+ 'content-type' : 'text/plain' ,
222+ } ,
223+ } ) ,
224+ hooks : {
225+ afterResponse : [
226+ ( { response} ) => new Response ( response . body , {
227+ headers : response . headers ,
228+ } ) ,
229+ ] ,
230+ } ,
231+ } ) . text ( ) ;
232+
233+ t . is ( responseText , 'ok' ) ;
234+ } ) ;
235+
236+ test ( 'afterResponse hook can wrap a streaming body in a new response' , async t => {
237+ const customFetch = createStreamFetch ( { text : 'streamed' } ) ;
238+
239+ const responseText = await ky ( 'https://example.com' , {
240+ fetch : customFetch ,
241+ hooks : {
242+ afterResponse : [
243+ ( { response} ) => new Response ( response . body , {
244+ headers : response . headers ,
245+ } ) ,
246+ ] ,
247+ } ,
248+ } ) . text ( ) ;
249+
250+ t . is ( responseText , 'streamed' ) ;
251+ } ) ;
252+
253+ test ( 'afterResponse hook chain can forward the same body through multiple hooks' , async t => {
254+ let originalResponse : Response | undefined ;
255+
256+ const customFetch = createStreamFetch ( {
257+ text : 'chained' ,
258+ onResponse ( response ) {
259+ originalResponse = response ;
260+ } ,
261+ } ) ;
262+
263+ const responseText = await ky ( 'https://example.com' , {
264+ fetch : customFetch ,
265+ hooks : {
266+ afterResponse : [
267+ ( { response} ) => new Response ( response . body , {
268+ headers : response . headers ,
269+ } ) ,
270+ ( { response} ) => new Response ( response . body , {
271+ headers : response . headers ,
272+ } ) ,
273+ ] ,
274+ } ,
275+ } ) . text ( ) ;
276+
277+ t . is ( responseText , 'chained' ) ;
278+ // The original fetch response body should be cancelled since hooks created new wrappers
279+ t . true ( originalResponse ?. bodyUsed ) ;
280+ } ) ;
281+
217282test ( 'afterResponse hook with multiple hooks cancels all unused clones' , async t => {
218283 let originalResponse : Response | undefined ;
219284 const clones : Response [ ] = [ ] ;
@@ -4725,6 +4790,81 @@ test('init hook in-place retry mutations do not leak across requests', async t =
47254790 t . deepEqual ( seenLimits , [ 2 , 2 ] ) ;
47264791} ) ;
47274792
4793+ test ( 'init hook nested retry mutations do not leak across requests' , async t => {
4794+ const seenMethods : string [ ] [ ] = [ ] ;
4795+
4796+ const api = ky . extend ( {
4797+ retry : {
4798+ methods : [ 'get' ] ,
4799+ } ,
4800+ hooks : {
4801+ init : [
4802+ options => {
4803+ seenMethods . push ( [ ...options . retry . methods ] ) ;
4804+ options . retry . methods . push ( 'post' ) ;
4805+ } ,
4806+ ] ,
4807+ } ,
4808+ } ) ;
4809+
4810+ const fetch : typeof globalThis . fetch = async ( ) => new Response ( 'ok' ) ;
4811+
4812+ await api . get ( 'https://example.com' , { fetch} ) ;
4813+ await api . get ( 'https://example.com' , { fetch} ) ;
4814+
4815+ t . deepEqual ( seenMethods , [ [ 'get' ] , [ 'get' ] ] ) ;
4816+ } ) ;
4817+
4818+ test ( 'init hook nested retry statusCodes mutations do not leak across requests' , async t => {
4819+ const seenStatusCodes : number [ ] [ ] = [ ] ;
4820+
4821+ const api = ky . extend ( {
4822+ retry : {
4823+ statusCodes : [ 500 ] ,
4824+ } ,
4825+ hooks : {
4826+ init : [
4827+ options => {
4828+ seenStatusCodes . push ( [ ...options . retry . statusCodes ] ) ;
4829+ options . retry . statusCodes . push ( 502 ) ;
4830+ } ,
4831+ ] ,
4832+ } ,
4833+ } ) ;
4834+
4835+ const fetch : typeof globalThis . fetch = async ( ) => new Response ( 'ok' ) ;
4836+
4837+ await api . get ( 'https://example.com' , { fetch} ) ;
4838+ await api . get ( 'https://example.com' , { fetch} ) ;
4839+
4840+ t . deepEqual ( seenStatusCodes , [ [ 500 ] , [ 500 ] ] ) ;
4841+ } ) ;
4842+
4843+ test ( 'init hook nested retry afterStatusCodes mutations do not leak across requests' , async t => {
4844+ const seenAfterStatusCodes : number [ ] [ ] = [ ] ;
4845+
4846+ const api = ky . extend ( {
4847+ retry : {
4848+ afterStatusCodes : [ 429 ] ,
4849+ } ,
4850+ hooks : {
4851+ init : [
4852+ options => {
4853+ seenAfterStatusCodes . push ( [ ...options . retry . afterStatusCodes ] ) ;
4854+ options . retry . afterStatusCodes . push ( 503 ) ;
4855+ } ,
4856+ ] ,
4857+ } ,
4858+ } ) ;
4859+
4860+ const fetch : typeof globalThis . fetch = async ( ) => new Response ( 'ok' ) ;
4861+
4862+ await api . get ( 'https://example.com' , { fetch} ) ;
4863+ await api . get ( 'https://example.com' , { fetch} ) ;
4864+
4865+ t . deepEqual ( seenAfterStatusCodes , [ [ 429 ] , [ 429 ] ] ) ;
4866+ } ) ;
4867+
47284868test ( 'init hook in-place json mutations do not leak across requests' , async t => {
47294869 let requestIdentifier = 0 ;
47304870 const seenRequestIdentifiers : string [ ] = [ ] ;
0 commit comments