"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostGraphileResponseFastify3 = exports.PostGraphileResponseKoa = exports.PostGraphileResponseNode = exports.PostGraphileResponse = void 0;
const stream_1 = require("stream");
/**
 * The base class for PostGraphile responses; collects headers, status code and
 * body, and then hands to the relevant adaptor at the correct time.
 */
class PostGraphileResponse {
    constructor() {
        this._headers = {};
        this._setHeaders = false;
        this.statusCode = 200;
    }
    _setHeadersOnce() {
        if (!this._setHeaders) {
            this._setHeaders = true;
            this.setHeaders(this.statusCode, this._headers);
        }
    }
    setHeader(header, value) {
        if (this._setHeaders) {
            throw new Error(`Cannot set a header '${header}' when headers already sent`);
        }
        this._headers[header] = value;
    }
    /**
     * Use `responseStream` or `end`; not both.
     */
    responseStream() {
        if (this._body != null) {
            throw new Error("Cannot return a stream when there's already a response body");
        }
        this._setHeadersOnce();
        this._body = new stream_1.PassThrough();
        this.setBody(this._body);
        return this._body;
    }
    /**
     * Use `responseStream` or `end`; not both
     */
    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 possible, set Content-Length to avoid unnecessary chunked encoding
        if (typeof this._body === 'string') {
            // String length is not reliable due to multi-byte characters; calculate via Buffer
            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);
    }
}
exports.PostGraphileResponse = PostGraphileResponse;
/**
 * Suitable for Node's HTTP server, but also connect, express, restify and fastify v2.
 */
class PostGraphileResponseNode extends PostGraphileResponse {
    constructor(req, res, next) {
        super();
        this._req = req;
        this._res = res;
        this._next = next;
    }
    getNodeServerRequest() {
        return this._req;
    }
    getNodeServerResponse() {
        return this._res;
    }
    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 {
            // Must be a stream
            // It'd be really nice if we could just:
            //
            //   body.pipe(this._res);
            //
            // however we need to support running within the compression middleware
            // which requires special handling for server-sent events:
            // https://github.com/expressjs/compression#server-sent-events
            //
            // Because of this, we must handle the data streaming manually so we can
            // flush:
            const writeData = (data) => {
                this._res.write(data);
                // Technically we should see if `.write()` returned false, and if so we
                // should pause the stream. However, since our stream is coming from
                // watch mode, we find it unlikely that a significant amount of data
                // will be buffered (and we don't recommend watch mode in production),
                // so it doesn't feel like we need this currently. If it turns out you
                // need this, a PR would be welcome.
                if (typeof this._res.flush === 'function') {
                    // https://github.com/expressjs/compression#server-sent-events
                    this._res.flush();
                }
                else if (typeof this._res.flushHeaders === 'function') {
                    this._res.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);
        }
    }
}
exports.PostGraphileResponseNode = PostGraphileResponseNode;
/**
 * Suitable for Koa.
 */
class PostGraphileResponseKoa extends PostGraphileResponse {
    constructor(ctx, next) {
        super();
        this._ctx = ctx;
        this._next = next;
        const req = this.getNodeServerRequest();
        // For backwards compatibility (this is a documented interface)
        req._koaCtx = ctx;
        // Make `koa-bodyparser` trigger skipping of our `body-parser`
        if (ctx.request.body) {
            req._body = true;
            req.body = ctx.request.body;
        }
        // In case you're using koa-mount or similar
        req.originalUrl = ctx.request.originalUrl;
    }
    getNodeServerRequest() {
        return this._ctx.req;
    }
    getNodeServerResponse() {
        return this._ctx.res;
    }
    setHeaders(statusCode, headers) {
        this._ctx.status = statusCode;
        this._ctx.set(headers);
        // DO NOT `this._ctx.flushHeaders()` as it will interfere with the compress
        // middleware.
    }
    responseStream() {
        // We're going to assume this is the EventStream which we want to
        // be realtime for watch mode, and there's no value in compressing it.
        this._ctx.compress = false;
        // TODO: find a better way of flushing the event stream on write.
        return super.responseStream();
    }
    setBody(body) {
        this._ctx.body = body || '';
        this._next();
    }
}
exports.PostGraphileResponseKoa = PostGraphileResponseKoa;
/**
 * Suitable for Fastify v3 (use PostGraphileResponseNode and middleware
 * approach for Fastify v2)
 */
class PostGraphileResponseFastify3 extends PostGraphileResponse {
    constructor(request, reply) {
        super();
        this._request = request;
        this._reply = reply;
        // Make Fastify's body parsing trigger skipping of our `body-parser`
        if (this._request.body) {
            const req = this.getNodeServerRequest();
            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);
    }
    responseStream() {
        // We're going to assume this is the EventStream which we want to
        // be realtime for watch mode, and there's no value in compressing it.
        this.setHeader('x-no-compression', '1');
        // TODO: this setHeader doesn't seem to actually fix the issue; had to
        // solve it in userland by adding `{ config: { compress: false } }` to the
        // route options.
        // TODO: find a better way of flushing the event stream on write.
        return super.responseStream();
    }
    setBody(body) {
        this._reply.send(body);
    }
}
exports.PostGraphileResponseFastify3 = PostGraphileResponseFastify3;
//# sourceMappingURL=data:application/json;base64, 