alpha-command-bus-rpc-server
Version:
RPC server over http for alpha-command-bus
124 lines (123 loc) • 4.94 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.rpcServer = void 0;
const express = require("express");
const alpha_serializer_1 = require("alpha-serializer");
const is = require("predicates");
const asyncHandler = require("express-async-handler");
const common_errors_1 = require("@pallad/common-errors");
const body_parser_1 = require("body-parser");
const typeIs = require("type-is");
const stream_1 = require("stream");
const busboy = require("busboy");
const isReadableStream_1 = require("./isReadableStream");
function createNonCommandBodyError() {
return new common_errors_1.RemoteServerError('Request body does not look like a command object. Make sure you have sent proper command object');
}
function rpcServer(commandBus, options = {}) {
const serializer = options.serializer || alpha_serializer_1.serializer;
const router = express.Router();
router.use((0, body_parser_1.raw)({
type: ['application/json']
}));
router.use(asyncHandler(async function (req, res) {
try {
const command = await createCommandFromRequest(req);
const result = await commandBus.handle(command);
if ((0, isReadableStream_1.isReadableStream)(result)) {
res.status(200).header('Content-Type', 'application/octet-stream');
result.pipe(res);
}
else {
res.status(200)
.header('Content-Type', 'application/json')
.send(serializer.serialize(result));
}
(options === null || options === void 0 ? void 0 : options.onResult) && options.onResult(result);
}
catch (e) {
res.status(200)
.header('X-command-bus-error', '1')
.header('Content-type', 'application/json')
.send(serializer.serialize(e));
(options === null || options === void 0 ? void 0 : options.onError) && options.onError(e);
}
}));
function getCommandBodyFromMultipart(serializer, req) {
return new Promise((resolve, reject) => {
let result = {};
const parser = busboy({
headers: req.headers,
fileHwm: 1024 * 1024 * 100
});
parser.on('file', (fieldname, file) => {
const finalFile = file;
result[fieldname] = finalFile.pipe(new stream_1.Transform({
highWaterMark: finalFile.readableHighWaterMark,
transform(chunk, encoding, callback) {
this.push(chunk);
callback();
}
}));
});
parser.on('field', (name, value) => {
if (name === 'commandBody') {
Object.assign(result, serializer.deserialize(value));
}
});
parser.on('finish', () => {
resolve(result);
});
parser.on('error', (e) => {
reject(e);
});
req.pipe(parser);
});
}
async function createCommandFromRequest(req) {
let command;
const mediaType = req.header('content-type');
if (!mediaType) {
throw new common_errors_1.RemoteServerError(`Missing content-type`);
}
if (typeIs.is(mediaType, ['json'])) {
const body = req.body;
if (is.empty(body)) {
throw new common_errors_1.RemoteServerError('Missing body for command');
}
try {
if (is.string(body)) {
command = serializer.deserialize(body);
}
else if (Buffer.isBuffer(body)) {
command = serializer.deserialize(body.toString('utf8'));
}
else {
command = serializer.normalizer.denormalize(body);
}
}
catch (e) {
throw new common_errors_1.RemoteServerError(`Cannot deserialize body: ${e.message}`);
}
}
else if (typeIs.is(mediaType, ['multipart'])) {
command = await getCommandBodyFromMultipart(serializer, req);
}
else {
throw new common_errors_1.RemoteServerError(`Unsupported content-type: ${req.header('content-type')}`);
}
if (!command) {
throw createNonCommandBodyError();
}
if (!is.prop('command', String)(command)) {
throw createNonCommandBodyError();
}
if (options.prepareCommand) {
command = await options.prepareCommand(command, req);
}
(options === null || options === void 0 ? void 0 : options.onCommand) && options.onCommand(command);
return command;
}
return router;
}
exports.rpcServer = rpcServer;