@axinom/mosaic-transactional-inbox-outbox
Version:
This library encapsulates the Mosaic based transactional inbox and outbox pattern
133 lines • 6.63 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RabbitMqInboxWriter = void 0;
const mosaic_message_bus_1 = require("@axinom/mosaic-message-bus");
const async_mutex_1 = require("async-mutex");
const common_1 = require("../common");
class RabbitMqInboxWriter extends mosaic_message_bus_1.MessageHandler {
/**
* Creates a new RabbitMQ-based handler that receives all events and commands
* and stores them in the transactional inbox.
* @param storeInboxMessage Function to store the incoming RabbitMQ message in the transactional inbox
* @param customizations Provide custom logic on how the inbox writer should process messages
*/
constructor(storeInboxMessage, ownerPool, logger, customizations) {
super(common_1.DEFAULT_INBOX_MESSAGE_TYPE);
this.storeInboxMessage = storeInboxMessage;
this.ownerPool = ownerPool;
this.logger = logger;
this.mutex = new async_mutex_1.Mutex();
//TODO: [feature/image-transactional-in-out-box] remove backwards compatibility after 2024-02-01
this.backwardCompatibilityMapper = (acceptedMessageSettings) => {
const backwardCompatibilityMappings = {};
for (const messageSettings of acceptedMessageSettings !== null && acceptedMessageSettings !== void 0 ? acceptedMessageSettings : []) {
backwardCompatibilityMappings[messageSettings.messageType] =
messageSettings.aggregateType;
}
return backwardCompatibilityMappings;
};
this.customMessageMapper = customizations === null || customizations === void 0 ? void 0 : customizations.customMessageMapper;
this.customMessagePreProcessor = customizations === null || customizations === void 0 ? void 0 : customizations.customMessagePreProcessor;
this.backwardCompatibilityMappings = this.backwardCompatibilityMapper(customizations === null || customizations === void 0 ? void 0 : customizations.acceptedMessageSettings);
}
/**
* Store the incoming message in the transactional inbox.
*/
async onMessage(payload, message) {
var _a, _b, _c;
// Using a mutex to ensure that each message is completely inserted
// in the original sort order into the inbox.
const release = await this.mutex.acquire();
let msgInput;
try {
if (this.customMessageMapper) {
msgInput = this.customMessageMapper(message);
}
if (!msgInput) {
const envelope = message.envelope;
let aggregateType = envelope.aggregate_type;
if (!aggregateType) {
//TODO: [feature/image-transactional-in-out-box] remove backwards compatibility after 2024-02-01
aggregateType =
(_a = this.backwardCompatibilityMappings[envelope.message_type]) !== null && _a !== void 0 ? _a : common_1.UNKNOWN_AGGREGATE_TYPE;
}
let aggregateId = envelope.aggregate_id;
if (!aggregateId) {
//TODO: [feature/image-transactional-in-out-box] remove backwards compatibility after 2024-02-01
aggregateId = String((_b = payload === null || payload === void 0 ? void 0 : payload.id) !== null && _b !== void 0 ? _b : common_1.UNKNOWN_AGGREGATE_ID);
}
msgInput = {
messageId: envelope.message_id,
aggregateId,
messagingSettings: {
aggregateType,
messageType: envelope.message_type,
},
payload,
metadata: {
envelopeTimestamp: envelope.timestamp,
messageVersion: envelope.message_version,
messageContext: envelope.message_context,
authToken: envelope.auth_token,
fields: message.fields,
properties: message.properties,
},
concurrency: 'parallel',
segment: unsafeGetEnvironment(envelope.auth_token),
};
}
(_c = this.customMessagePreProcessor) === null || _c === void 0 ? void 0 : _c.call(this, msgInput);
const { payload: p, aggregateId, messagingSettings } = msgInput, optionalData = __rest(msgInput, ["payload", "aggregateId", "messagingSettings"]);
// No transaction for single insert needed - directly using the pool
await this.storeInboxMessage(aggregateId, messagingSettings, p, this.ownerPool, optionalData);
this.logger.debug({
message: 'Message added to the inbox table.',
details: Object.assign({}, msgInput),
});
}
catch (error) {
const err = error instanceof Error
? error
: new Error(String(error !== null && error !== void 0 ? error : 'unknown error'));
this.logger.error(err, {
message: 'Could not write the message to the inbox table.',
details: Object.assign({}, (msgInput !== null && msgInput !== void 0 ? msgInput : message)),
});
throw err;
}
finally {
release();
}
}
}
exports.RabbitMqInboxWriter = RabbitMqInboxWriter;
/**
* Get the environment ID by parsing the JWT without checking the signature.
* This is only used for "fair message processing" - the actual JWT check is
* done in the message handler.
*/
const unsafeGetEnvironment = (token) => {
var _a;
if (!token) {
return undefined;
}
try {
const parsed = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
return (_a = parsed === null || parsed === void 0 ? void 0 : parsed.environmentId) !== null && _a !== void 0 ? _a : undefined;
}
catch (_b) {
return undefined;
}
};
//# sourceMappingURL=rabbitmq-inbox-writer.js.map
;