@fabric-es/fabric-cqrs
Version:
Hyperledger Fabric middleware for event sourcing and cqrs pattern
196 lines • 11 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createQueryHandler = void 0;
const util_1 = __importDefault(require("util"));
const debug_1 = __importDefault(require("debug"));
const store_1 = require("../store");
const projection_1 = require("../store/projection");
const query_1 = require("../store/query");
const reconcile_1 = require("../store/reconcile");
const utils_1 = require("../utils");
const constants_1 = require("./constants");
const createQueryHandler = (options) => {
const debug = debug_1.default('queryHandler:createQueryHandler');
const { entityNames, gateway, queryDatabase, channelName, wallet, connectionProfile, reducers, pubSub, } = options;
const logger = utils_1.getLogger({ name: '[query-handler] createQueryHandler.js' });
options.queryDatabase = queryDatabase;
options.logger = logger;
const store = store_1.getStore(options);
let contractListener;
let contract;
let network;
const commandOption = { logger, wallet, store, connectionProfile, channelName };
const queryOption = { logger, store };
let isReconciled = false;
return {
clearNotification: async ({ creator, entityName, id, commitId }) => utils_1.dispatcher((payload) => query_1.action.clearNotification(payload), {
store,
logger,
name: 'query:clearNotification',
slice: 'query',
SuccessAction: query_1.action.CLEAR_NOTI_SUCCESS,
ErrorAction: query_1.action.CLEAR_NOTI_ERROR,
})({ creator, entityName, id, commitId }),
clearNotifications: async ({ creator, entityName, id }) => utils_1.dispatcher((payload) => query_1.action.clearNotifications(payload), {
store,
logger,
name: 'query:clearNotifications',
slice: 'query',
SuccessAction: query_1.action.CLEAR_NOTI_SUCCESS,
ErrorAction: query_1.action.CLEAR_NOTI_ERROR,
})({ creator, entityName, id }),
create: (entityName) => {
if (!entityNames.includes(entityName))
throw new Error(constants_1.INVALID_ARG);
return utils_1.commandCreate(entityName, false, commandOption);
},
command_deleteByEntityId: (entityName) => {
if (!entityNames.includes(entityName))
throw new Error(constants_1.INVALID_ARG);
return utils_1.commandDeleteByEntityId(entityName, false, commandOption);
},
command_getByEntityName: (entityName) => {
if (!entityNames.includes(entityName))
throw new Error(constants_1.INVALID_ARG);
return utils_1.commandGetByEntityName(entityName, false, commandOption);
},
getById: (entityName) => utils_1.queryGetById(entityName, reducers[entityName], false, commandOption),
getByEntityName: (entityName) => utils_1.queryGetEntityByEntityName(entityName, reducers[entityName], queryOption),
getCommitById: (entityName) => utils_1.queryGetCommitByEntityId(entityName, queryOption),
query_deleteCommitByEntityId: (entityName) => utils_1.queryDeleteCommitByEntityId(entityName, queryOption),
query_deleteCommitByEntityName: (entityName) => utils_1.queryDeleteCommitByEntityName(entityName, queryOption),
query_deleteEntityByEntityName: (entityName) => () => utils_1.dispatcher((payload) => query_1.action.deleteEntityByEntityName(payload), {
store,
logger,
slice: 'query',
name: 'query:deleteEntityByEntityName',
SuccessAction: query_1.action.DELETE_ENTITY_SUCCESS,
ErrorAction: query_1.action.DELETE_ENTITY_ERROR,
})({ entityName }),
fullTextSearchCommit: async ({ query, param, cursor, pagesize }) => utils_1.queryFullTextSearch({ store, logger, query, param, cursor, pagesize }),
fullTextSearchEntity: async ({ query, param, cursor, pagesize, entityName }) => utils_1.queryFullTextSearch({ store, logger, query, param, cursor, pagesize, entityName }),
getNotification: async ({ creator, entityName, id, commitId }) => utils_1.dispatcher((payload) => query_1.action.getNotifications(payload), {
store,
logger,
name: 'query:getNotification',
slice: 'query',
SuccessAction: query_1.action.GET_NOTI_SUCCESS,
ErrorAction: query_1.action.GET_NOTI_ERROR,
})({ creator, entityName, id, commitId }),
getNotifications: async ({ creator, entityName, id }) => utils_1.dispatcher((payload) => query_1.action.getNotifications(payload), {
store,
logger,
name: 'query:getNotifications',
slice: 'query',
SuccessAction: query_1.action.GET_NOTI_SUCCESS,
ErrorAction: query_1.action.GET_NOTI_ERROR,
})({ creator, entityName, id }),
disconnect: () => gateway.disconnect(),
reconcile: (payload) => (() => utils_1.dispatcher(({ tx_id, args }) => reconcile_1.action.reconcile({
tx_id,
args,
store,
channelName,
connectionProfile,
wallet,
}), {
name: 'reconcile',
store,
slice: 'reconcile',
SuccessAction: reconcile_1.action.RECONCILE_SUCCESS,
ErrorAction: reconcile_1.action.RECONCILE_ERROR,
logger,
}))()(payload).then((result) => {
isReconciled = true;
return result;
}),
reconciled: () => (isReconciled = true),
subscribeHub: async (entityNames) => {
logger.info('♨️ subscribe channel event hub');
try {
network = await gateway.getNetwork(channelName);
}
catch (err) {
logger.error(util_1.default.format('❌ failed to subscribeHub. cannot getNetwork from gateway', err));
return Promise.reject(new Error('failed to subscribeHub. cannot getNetwork from gateway'));
}
contract = network.getContract('eventstore');
try {
contractListener = await network.getContract('eventstore').addContractListener(async ({ payload, eventName, getTransactionEvent }) => {
var _a;
logger.info(`💢 event arrives - tx_id: ${getTransactionEvent().transactionId}`);
if (!isReconciled) {
logger.info(`pending reconciliation, skipping 'merge_one_entity'...`);
return;
}
let commit;
if (eventName !== 'createCommit') {
logger.warn(`receive unexpected contract event: ${eventName}`);
return;
}
try {
commit = JSON.parse(payload.toString('utf8'));
}
catch (e) {
logger.error(util_1.default.format('fail to parse contract event, %j', e));
return;
}
if (utils_1.isCommit(commit)) {
logger.info(`💢 event arrives - commitId: ${commit.id} found`);
if (!entityNames.includes(commit === null || commit === void 0 ? void 0 : commit.entityName)) {
logger.info(util_1.default.format('receive commit of unsubscribed entityName, %s:%s', commit.entityName, commit.id));
return;
}
const mergeEntityResult = await utils_1.dispatcher((payload) => projection_1.action.mergeEntity(payload), {
name: 'merge_one_entity',
store,
slice: 'projection',
SuccessAction: projection_1.action.MERGE_ENTITY_SUCCESS,
ErrorAction: projection_1.action.MERGE_ENTITY_ERROR,
logger,
})({ commit });
if (mergeEntityResult.status === 'OK') {
debug('subscribeHub:mergeEntityResult, %O', mergeEntityResult);
logger.info(util_1.default.format('mergeComit: %j', (mergeEntityResult === null || mergeEntityResult === void 0 ? void 0 : mergeEntityResult.data) || 'no data written'));
}
else
logger.error(util_1.default.format('%s ❌ fail to mergeEntity, %j', constants_1.FATAL, mergeEntityResult));
if (pubSub && mergeEntityResult.status === 'OK') {
const events = [];
commit.events.forEach((event) => events.push(event === null || event === void 0 ? void 0 : event.type));
await pubSub
.publish('COMMIT_ARRIVED', {
entityAdded: {
commit,
events,
key: (_a = mergeEntityResult === null || mergeEntityResult === void 0 ? void 0 : mergeEntityResult.data[0]) === null || _a === void 0 ? void 0 : _a.key,
},
})
.catch((e) => {
logger.error(util_1.default.format('fail to publish commit, %j, %j ', commit, e));
});
}
else if (pubSub && mergeEntityResult.status !== 'OK') {
await pubSub.publish('SYSTEM_EVENT', {
systemEvent: Object.assign(Object.assign({}, mergeEntityResult), { error: JSON.stringify(mergeEntityResult === null || mergeEntityResult === void 0 ? void 0 : mergeEntityResult.error), event: 'FAIL_TO_MERGE_ENTITY_TO_QDB', timestamp: Date.now() }),
});
}
}
else
logger.error(util_1.default.format('receive commit of unknown type, %j', commit));
}, { type: 'full' });
}
catch (err) {
logger.error(util_1.default.format('❌ failed to subscribeHub. cannot addContractlistner', err));
return Promise.reject(new Error('failed to subscribeHub. cannot addContractlistner'));
}
return true;
},
unsubscribeHub: () => contract.removeContractListener(contractListener),
};
};
exports.createQueryHandler = createQueryHandler;
//# sourceMappingURL=createQueryHandler.js.map