@@ -36,20 +36,27 @@ function checkNodeVersion(): void {
3636
3737export type { H2PoolOptions as H2FetchOptions } ;
3838
39- function normalizeBody ( body : unknown ) : string | Buffer | null {
39+ export type H2Fetch = Fetch & {
40+ close : ( ) => Promise < void > ;
41+ } ;
42+
43+ async function bufferReadable ( stream : Readable ) : Promise < Buffer > {
44+ const chunks : Buffer [ ] = [ ] ;
45+ for await ( const chunk of stream ) {
46+ chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) ) ;
47+ }
48+ return Buffer . concat ( chunks ) ;
49+ }
50+
51+ async function normalizeBody ( body : unknown ) : Promise < string | Buffer | null > {
4052 if ( body == null ) return null ;
4153 if ( typeof body === 'string' ) return body ;
4254 if ( Buffer . isBuffer ( body ) ) return body ;
4355 if ( body instanceof MultipartBody ) return normalizeBody ( ( body as MultipartBody ) . body ) ;
4456 if ( body instanceof Readable ) {
45- // Multipart bodies arrive as Node Readable. For H2, we need to buffer them
46- // since session.request() takes string | Buffer. The Readable is a
47- // FormDataEncoder stream with a known content-length, so buffering is safe.
48- // Streaming uploads could be added later if needed.
49- throw new Error (
50- 'h2-transport: streaming request bodies (Readable) are not yet supported. ' +
51- 'Use a string or Buffer body.' ,
52- ) ;
57+ // Multipart bodies arrive as a FormDataEncoder Readable with a known
58+ // content-length. session.request() takes string | Buffer, so buffer here.
59+ return bufferReadable ( body ) ;
5360 }
5461 if ( ArrayBuffer . isView ( body ) ) return Buffer . from ( body . buffer , body . byteOffset , body . byteLength ) ;
5562 if ( body instanceof ArrayBuffer ) return Buffer . from ( body ) ;
@@ -62,7 +69,7 @@ function normalizeBody(body: unknown): string | Buffer | null {
6269 * Compatible with the SDK's `makeHttp2Fetch` interface: called with no arguments
6370 * for `http2: true`, or with options for `http2: <H2FetchOptions>`.
6471 */
65- export function createH2Fetch ( options ?: H2PoolOptions ) : Fetch {
72+ export function createH2Fetch ( options ?: H2PoolOptions ) : H2Fetch {
6673 checkNodeVersion ( ) ;
6774 const pools = new Map < string , H2Pool > ( ) ;
6875
@@ -75,7 +82,7 @@ export function createH2Fetch(options?: H2PoolOptions): Fetch {
7582 return pool ;
7683 }
7784
78- return async ( url , init ) => {
85+ const h2Fetch = ( async ( url , init ) => {
7986 const {
8087 agent : _ignored , // node-fetch artifact injected by core.ts
8188 body : rawBody ,
@@ -86,7 +93,7 @@ export function createH2Fetch(options?: H2PoolOptions): Fetch {
8693
8794 const parsed = typeof url === 'string' ? new URL ( url ) : new URL ( url . toString ( ) ) ;
8895 const path = parsed . pathname + parsed . search ;
89- const body = normalizeBody ( rawBody ) ;
96+ const body = await normalizeBody ( rawBody ) ;
9097
9198 const reqHeaders : Record < string , string > = { } ;
9299 if ( rawHeaders ) {
@@ -103,5 +110,13 @@ export function createH2Fetch(options?: H2PoolOptions): Fetch {
103110
104111 const pool = getPool ( parsed . origin ) ;
105112 return pool . request ( path , method . toUpperCase ( ) , reqHeaders , body , signal ) as any ;
113+ } ) as H2Fetch ;
114+
115+ h2Fetch . close = async ( ) => {
116+ const closeTasks = [ ...pools . values ( ) ] . map ( ( pool ) => pool . close ( ) ) ;
117+ pools . clear ( ) ;
118+ await Promise . all ( closeTasks ) ;
106119 } ;
120+
121+ return h2Fetch ;
107122}
0 commit comments