textbook/express-msgpack

View on GitHub
src/index.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
96%
import { NextFunction, Request, RequestHandler } from "express";
import readBody from "raw-body";

export interface ExpressMsgpackOptions {
    encoder: (body: unknown) => Buffer;
    decoder: (body: Buffer) => unknown;
    limit: string;
    mimeType: string;
}

export default (overrides: Partial<ExpressMsgpackOptions> = {}): RequestHandler => {
    const optionsPromise = createOptions(overrides);

    return async (req, res, next) => {
        try {
            const options = await optionsPromise;
            // Handle response
            const _json = res.json;
            res.json = (body) => {
                return res.format({
                    "application/json": () => _json.call(res, body),
                    [options.mimeType]: () => res.send(options.encoder(body)),
                });
            };

            // Handle request
            if (new RegExp(`^${options.mimeType}`, "i").test(req.header("Content-Type") ?? "")) {
                return readBody(
                    req,
                    { length: req.header("Content-Length"), limit: options.limit },
                    bodyHandler(options, req, next)
                );
            }

            next();
        } catch (err) {
            next(err);
        }
    };
};

type ReadBodyCallback = (err: Error, body: Buffer) => void;

const bodyHandler = (options: ExpressMsgpackOptions, req: Request, next: NextFunction): ReadBodyCallback => (err, body) => {
    if (err) {
        return next(err);
    }
    try {
        req.body = options.decoder(body);
    } catch (err) {
        return next(err);
    }
    (req as Request & { _body: boolean })._body = true;
    next();
};

const createOptions = async (overrides: Partial<ExpressMsgpackOptions>): Promise<ExpressMsgpackOptions> => {
    return {
        decoder: overrides.decoder
            ?? await import("@msgpack/msgpack").then(({ decode }) => decode),
        encoder: overrides.encoder
            ?? await import("@msgpack/msgpack").then(({ encode }) => (body) => Buffer.from(encode(body))),
        limit: overrides.limit
            ?? "100kb",
        mimeType: overrides.mimeType
            ?? "application/msgpack",
    };
};