@arpinum/messaging
Version:
Simple message bus
146 lines (145 loc) • 6.35 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultMessageBus = void 0;
const promising_1 = require("@arpinum/promising");
const asserts_1 = require("./asserts");
const defaultOptions = {
log: () => {
//noop
},
exclusiveHandlers: false,
ensureAtLeastOneHandler: false,
handlersConcurrency: 3,
beforePost: [],
afterPost: [],
beforeHandle: [],
afterHandle: [],
};
class DefaultMessageBus {
constructor(options = {}) {
this.validateOptions(options);
this.options = Object.assign({}, defaultOptions, options);
this.handlerMap = new Map();
this.beforeHandle = (0, promising_1.pipe)(this.options.beforeHandle);
this.afterHandle = (0, promising_1.pipe)(this.options.afterHandle);
this.beforePost = (0, promising_1.pipe)(this.options.beforePost);
this.afterPost = (0, promising_1.pipe)(this.options.afterPost);
}
validateOptions(options) {
(0, asserts_1.assertOptionalFunction)(options.log, "options#log");
(0, asserts_1.assertOptionalBoolean)(options.exclusiveHandlers, "options#exclusiveHandlers");
(0, asserts_1.assertOptionalBoolean)(options.ensureAtLeastOneHandler, "options#ensureAtLeastOneHandler");
(0, asserts_1.assertOptionalNumber)(options.handlersConcurrency, "options#handlersConcurrency");
(0, asserts_1.assertOptionalArray)(options.beforePost, "options#beforePost");
(0, asserts_1.assertOptionalArray)(options.beforeHandle, "options#beforeHandle");
(0, asserts_1.assertOptionalArray)(options.afterHandle, "options#afterHandle");
(0, asserts_1.assertOptionalArray)(options.afterPost, "options#afterPost");
}
postAll(messages) {
if (messages.length === 0) {
return Promise.resolve([]);
}
if (messages.length === 1) {
return this.post(messages[0]).then((r) => [r]);
}
return (0, promising_1.mapWithOptions)((message) => this.post(message), { concurrency: 3 }, messages);
}
post(message) {
return this.validatedMessage(message)
.then(this.beforePost)
.then((message) => this.postToHandlers(message))
.then(this.afterPost);
}
postToHandlers(messageToPost) {
this.options.log(`Posting ${messageToPost.type}`);
const handlers = this.handlersFor(messageToPost.type);
this.checkHandlerRequirements(messageToPost, handlers);
if (this.options.exclusiveHandlers) {
return this.postForExclusiveHandlers(messageToPost, handlers);
}
return this.postForStandardHandlers(messageToPost, handlers);
}
validatedMessage(messageToValidate) {
try {
(0, asserts_1.assertPresent)(messageToValidate, "message");
(0, asserts_1.assertString)(messageToValidate.type, "message#type");
return Promise.resolve(messageToValidate);
}
catch (e) {
return Promise.reject(e);
}
}
checkHandlerRequirements(messageToCheck, handlers) {
if (handlers.length === 0 && this.options.ensureAtLeastOneHandler) {
throw new Error(`No handler for ${messageToCheck.type}`);
}
}
postForExclusiveHandlers(messageToPost, handlers) {
return __awaiter(this, void 0, void 0, function* () {
if (handlers.length === 0) {
return Promise.resolve([]);
}
const result = yield this.handle(messageToPost, handlers[0]);
return [result];
});
}
postForStandardHandlers(messageToPost, handlers) {
if (handlers.length === 0) {
return Promise.resolve([]);
}
if (handlers.length === 1) {
return this.handle(messageToPost, handlers[0]).then((r) => [r]);
}
const handleMessage = (handler) => this.handle(messageToPost, handler);
return (0, promising_1.mapWithOptions)(handleMessage, { concurrency: this.options.handlersConcurrency }, handlers);
}
handle(message, handler) {
return this.beforeHandle(message).then(handler).then(this.afterHandle);
}
register(type, handler) {
validateArgs();
this.options.log(`Registering to ${type}`);
const handlers = this.handlersFor(type);
this.ensureHandlerExclusivity(handlers, type);
this.updateHandlers(type, handlers.concat(handler));
return () => this.updateHandlers(type, this.handlersFor(type).filter((h) => h !== handler));
function validateArgs() {
(0, asserts_1.assertPresent)(type, "type");
(0, asserts_1.assertFunction)(handler, "handler");
}
}
ensureHandlerExclusivity(handlers, type) {
if (this.options.exclusiveHandlers && handlers.length > 0) {
const message = `Won't allow a new handler for type ${type} ` +
`since handlers are exclusive`;
throw new Error(message);
}
}
unregisterAll(...types) {
validateArgs();
types.forEach((type) => this.updateHandlers(type, []));
function validateArgs() {
types.forEach((type, i) => (0, asserts_1.assertString)(type, `types[${i}]`));
}
}
handlerCount(type) {
(0, asserts_1.assertString)(type, "type");
return this.handlersFor(type).length;
}
handlersFor(messageType) {
return this.handlerMap.get(messageType) || [];
}
updateHandlers(messageType, handlers) {
this.handlerMap.set(messageType, handlers);
}
}
exports.DefaultMessageBus = DefaultMessageBus;