import { PassThrough , Stream } from 'stream' ; import type { IncomingMessage , ServerResponse } from 'http' ; /******************************************************************************/ export interface CompatFastifyReply { raw : ServerResponse ; status ( statusCode ) : CompatFastifyReply ; headers ( values ) : CompatFastifyReply ; send ( payload ? ) : CompatFastifyReply ; } export interface CompatFastifyRequest { raw : IncomingMessage ; body : unknown ; readonly headers : { [ key ] : unknown ; } ; } /******************************************************************************/ /******************************************************************************/ export interface CompatKoaContext { [ key ] : any ; req : IncomingMessage ; res : ServerResponse ; } /******************************************************************************/ declare module 'http' { interface IncomingMessage { _koaCtx ? : CompatKoaContext ; _fastifyRequest ? : CompatFastifyRequest ; _body ? : boolean ; body ? : any ; originalUrl ? : string ; } } type Headers = { [ header ] : string ; } ; /**
 * The base class for PostGraphile responses; collects headers, status code and
 * body, and then hands to the relevant adaptor at the correct time.
 */ export abstract class PostGraphileResponse { private _headers : Headers = { } ; private _body : Buffer | string | PassThrough | undefined ; private _setHeaders = false ; public statusCode = 200 ; private _setHeadersOnce ( ) { if ( ! this . _setHeaders ) { this . _setHeaders = true ; this . setHeaders ( this . statusCode , this . _headers ) ; } } public setHeader ( header , value ) { if ( this . _setHeaders ) { throw new Error ( `Cannot set a header '${ header }' when headers already sent` ) ; } this . _headers [ header ] = value ; } /**
     * Use `endWithStream` or `end`; not both.
     */ public endWithStream ( ) { if ( this . _body != null ) { throw new Error ( "Cannot return a stream when there's already a response body" ) ; } this . _setHeadersOnce ( ) ; this . _body = new PassThrough ( ) ; this . setBody ( this . _body ) ; return this . _body ; } /**
     * Use `endWithStream` or `end`; not both
     */ public end ( moreBody ? ) { if ( moreBody ) { if ( this . _body != null ) { if ( typeof this . _body === 'string' ) { if ( Buffer . isBuffer ( moreBody ) ) { throw new Error ( 'Cannot mix string and buffer' ) ; } this . _body = this . _body + moreBody ; } else if ( Buffer . isBuffer ( this . _body ) ) { if ( typeof moreBody === 'string' ) { throw new Error ( 'Cannot mix buffer and string' ) ; } this . _body = Buffer . concat ( [ this . _body , moreBody ] ) ; } else { throw new Error ( "Can't `.end(string)` when body is a stream" ) ; } } else { this . _body = moreBody ; } } if ( typeof this . _body === 'string' ) { this . setHeader ( 'Content-Length' , String ( Buffer . byteLength ( this . _body , 'utf8' ) ) ) ; } else if ( Buffer . isBuffer ( this . _body ) ) { this . setHeader ( 'Content-Length' , String ( this . _body . byteLength ) ) ; } this . _setHeadersOnce ( ) ; this . setBody ( this . _body ) ; } /**
     * Returns the `res` object that the underlying HTTP server would have.
     */ public abstract getNodeServerRequest ( ) ; public abstract getNodeServerResponse ( ) ; public abstract setHeaders ( statusCode , headers ) ; public abstract setBody ( body ) ; } /**
 * Suitable for Node's HTTP server, but also connect, express, restify and fastify v2.
 */ export class PostGraphileResponseNode extends PostGraphileResponse { private _req : IncomingMessage ; private _res : ServerResponse ; private _next : ( e ? : 'route' | Error ) => void ; constructor ( req , res , next ) { super ( ) ; this . _req = req ; this . _res = res ; this . _next = next ; } getNodeServerRequest ( ) { return this . _req ; } getNodeServerResponse ( ) { return this . _res ; } getNextCallback ( ) { return this . _next ; } setHeaders ( statusCode , headers ) { for ( const key in headers ) { if ( Object . hasOwnProperty . call ( headers , key ) ) { this . _res . setHeader ( key , headers [ key ] ) ; } } this . _res . statusCode = statusCode ; } setBody ( body ) { if ( typeof body === 'string' ) { this . _res . end ( body ) ; } else if ( Buffer . isBuffer ( body ) ) { this . _res . end ( body ) ; } else if ( ! body ) { this . _res . end ( ) ; } else { const writeData = ( data ) => { this . _res . write ( data ) ; if ( typeof ( this . _res as any ) . flush === 'function' ) { ( this . _res as any ) . flush ( ) ; } else if ( typeof ( this . _res as any ) . flushHeaders === 'function' ) { ( this . _res as any ) . flushHeaders ( ) ; } } ; let clean = false ; const cleanup = ( ) => { if ( clean ) return ; clean = true ; body . removeListener ( 'data' , writeData ) ; body . removeListener ( 'end' , cleanup ) ; this . _req . removeListener ( 'close' , cleanup ) ; this . _req . removeListener ( 'end' , cleanup ) ; this . _req . removeListener ( 'error' , cleanup ) ; } ; body . on ( 'data' , writeData ) ; body . on ( 'end' , ( ) => { cleanup ( ) ; this . _res . end ( ) ; } ) ; this . _req . on ( 'close' , cleanup ) ; this . _req . on ( 'end' , cleanup ) ; this . _req . on ( 'error' , cleanup ) ; } } } export type CompatKoaNext = ( error ? : Error ) => Promise < any > ; /**
 * Suitable for Koa.
 */ export class PostGraphileResponseKoa extends PostGraphileResponse { private _ctx : CompatKoaContext ; private _next : CompatKoaNext ; constructor ( ctx , next ) { super ( ) ; this . _ctx = ctx ; this . _next = next ; const req = this . getNodeServerRequest ( ) ; req . _koaCtx = ctx ; if ( ctx . request . body ) { req . _body = true ; req . body = ctx . request . body ; } req . originalUrl = ctx . request . originalUrl ; } getNodeServerRequest ( ) { return this . _ctx . req ; } getNodeServerResponse ( ) { return this . _ctx . res ; } getNextCallback ( ) { return this . _next ; } setHeaders ( statusCode , headers ) { this . _ctx . status = statusCode ; this . _ctx . set ( headers ) ; } endWithStream ( ) { this . _ctx . compress = false ; return super . endWithStream ( ) ; } setBody ( body ) { this . _ctx . body = body || '' ; this . _next ( ) ; } } /**
 * Suitable for Fastify v3 (use PostGraphileResponseNode and middleware
 * approach for Fastify v2)
 */ export class PostGraphileResponseFastify3 extends PostGraphileResponse { private _request : CompatFastifyRequest ; private _reply : CompatFastifyReply ; constructor ( request , reply ) { super ( ) ; this . _request = request ; this . _reply = reply ; const req = this . getNodeServerRequest ( ) ; req . _fastifyRequest = this . _request ; if ( this . _request . body ) { req . _body = true ; req . body = this . _request . body ; } } getNodeServerRequest ( ) { return this . _request . raw ; } getNodeServerResponse ( ) { return this . _reply . raw ; } setHeaders ( statusCode , headers ) { this . _reply . status ( statusCode ) ; this . _reply . headers ( headers ) ; } endWithStream ( ) { this . _request . headers [ 'x-no-compression' ] = '1' ; return super . endWithStream ( ) ; } setBody ( body ) { this . _reply . send ( body ) ; } }