event-driven
Version:
makes building event-driven-architecture easy
167 lines (166 loc) • 7.45 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());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriberBatch = void 0;
const models_1 = require("./backend/models");
const mongoose_1 = require("mongoose");
const nats_1 = require("nats");
const _1 = require(".");
const sc = nats_1.StringCodec();
const SubscriberBatch = ({ clientGroup, js, batch, expires, }) => {
return ({ subject, batchEventHandlers, }) => __awaiter(void 0, void 0, void 0, function* () {
for (let i = 0; i < batchEventHandlers.length; i++) {
const durableName = `${clientGroup}=${subject.replace(/\./g, '-')}=1=${i}`;
try {
let msgs = [];
const opts = nats_1.consumerOpts();
opts.ackWait(30000);
opts.manualAck();
opts.durable(durableName);
opts.maxAckPending(500);
const psub = yield js.pullSubscribe(subject, opts);
(() => __awaiter(void 0, void 0, void 0, function* () {
var e_1, _a;
try {
for (var psub_1 = __asyncValues(psub), psub_1_1; psub_1_1 = yield psub_1.next(), !psub_1_1.done;) {
const m = psub_1_1.value;
msgs.push(m);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (psub_1_1 && !psub_1_1.done && (_a = psub_1.return)) yield _a.call(psub_1);
}
finally { if (e_1) throw e_1.error; }
}
}))();
setInterval(() => {
if (msgs.length > 0) {
processEvents(msgs, _1._maxRetryCount, js, clientGroup, batchEventHandlers[i], durableName, subject, i, _1._showError, _1._showProcessTimeWarning);
}
msgs = [];
}, 100);
psub.pull({ batch, expires });
setInterval(() => {
psub.pull({ batch, expires });
}, expires);
}
catch (err) {
console.error(`clientGroup: ${clientGroup}, index: ${i}, subject: ${subject}`);
throw err;
}
}
});
};
exports.SubscriberBatch = SubscriberBatch;
const processEvents = (_msgs, maxRetryCount, js, clientGroup, batchEventHandler, durableName, subject, eventHandlerOrder, showError, showProcessTimeWarning) => __awaiter(void 0, void 0, void 0, function* () {
const msgs = [..._msgs];
const failedParentIds = [];
const events = msgs
.map((jsMsg) => {
jsMsg.info;
const headers = jsMsg.headers;
const eventId = headers === null || headers === void 0 ? void 0 : headers.get('eventId');
const parentId = new mongoose_1.Types.ObjectId(eventId);
const msgData = JSON.parse(sc.decode(jsMsg.data));
const retryingDurableName = headers === null || headers === void 0 ? void 0 : headers.get('retryingDurableName');
const fail = getFail({
failedParentIds,
jsMsg,
parentId,
durableName,
clientGroup,
subject,
eventHandlerOrder,
maxRetryCount,
showError,
});
return { jsMsg, parentId, msgData, retryingDurableName, fail };
})
.filter((args) => {
if (args.retryingDurableName && args.retryingDurableName !== durableName) {
args.jsMsg.ack();
return false;
}
else if (args.jsMsg.info.redeliveryCount > maxRetryCount) {
args.jsMsg.ack();
args.fail(new Error('__NO_ACK__'));
return false;
}
else
return true;
})
.map(({ jsMsg, parentId, msgData, fail }) => {
return {
parentId,
input: msgData,
jsMsg,
fail,
};
});
const log = models_1.createBatch({
parentEventIds: [...new Set(events.map((event) => event.parentId.toString()))].map((id) => new mongoose_1.Types.ObjectId(id)),
durableName,
clientGroup,
subject,
});
try {
yield batchEventHandler({
config: {
js,
clientGroup,
parentIds: [log.batch._id],
durableName,
},
events,
});
let diff = new Date().getTime() - log.batch.createdAt.getTime();
if (showProcessTimeWarning && showProcessTimeWarning < diff)
console.log('\x1b[33m%s\x1b[0m', `[BATCH-WARNING-TIME] ${diff / 1000}s, durableName: ${durableName}`);
events.filter((e) => !failedParentIds.find((pId) => pId.toString() === e.parentId.toString())).map((msg) => msg.jsMsg.ack());
log.batch.processTime = diff;
}
catch (error) {
log.catchError(error);
console.error('\x1b[31m%s\x1b[0m', `UNHANDLED SUBSCRIPTION BATCH ERRROR(${durableName})`, error);
}
finally {
yield log.batch.save();
}
});
const getFail = ({ failedParentIds, jsMsg, parentId, durableName, clientGroup, subject, maxRetryCount, showError, }) => (error) => __awaiter(void 0, void 0, void 0, function* () {
failedParentIds.push(parentId);
const eventError = yield models_1.createEventError({
eventId: parentId,
durableName,
clientGroup,
subject,
error,
});
const { errorCount } = eventError;
console.error('\x1b[31m%s\x1b[0m', `Failed ${errorCount} times, durableName: ${durableName}, errMsg: ${error.message}, eventId:${parentId}`);
if (showError)
console.error('\x1b[31m%s\x1b[0m', `${durableName}`, error.stack);
if (errorCount >= maxRetryCount) {
console.log('\x1b[41m%s\x1b[0m', `Error added to dead letter queue`);
jsMsg.ack();
}
else
jsMsg.nak((errorCount - 1) * 5000);
});