@fabric-es/fabric-cqrs
Version:
Hyperledger Fabric middleware for event sourcing and cqrs pattern
347 lines • 17 kB
JavaScript
;
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); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createQueryDatabase = void 0;
const util_1 = __importDefault(require("util"));
const debug_1 = __importDefault(require("debug"));
const filter_1 = __importDefault(require("lodash/filter"));
const groupBy_1 = __importDefault(require("lodash/groupBy"));
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const types_1 = require("../types");
const utils_1 = require("../utils");
const constants_1 = require("./constants");
const model_1 = require("./model");
const _1 = require(".");
const createQueryDatabase = (client, repos, { notifyExpiryBySec } = { notifyExpiryBySec: 86400 }) => {
const debug = debug_1.default('queryHandler:createQueryDatabase');
const logger = utils_1.getLogger({ name: '[query-handler] createQueryDatabase.js', target: 'console' });
const commitRepo = _1.createRedisRepository('commit', {
client,
kind: 'commit',
fields: model_1.commitSearchDefinition,
preSelector: model_1.preSelector,
postSelector: model_1.postSelector,
});
const notificationCenter = _1.createNotificationCenter(client);
const allRepos = Object.assign(repos, { commit: commitRepo });
const deleteItems = async (repo, pattern) => {
const [errors, count] = await repo.deleteItemsByPattern(pattern);
const isError = errors === null || errors === void 0 ? void 0 : errors.reduce((pre, cur) => pre || !!cur, false);
return isError
? {
status: 'ERROR',
message: `${count} record(s) deleted`,
errors,
}
: {
status: 'OK',
message: `${count} record(s) deleted`,
data: count,
};
};
const queryCommit = async (pattern, args) => {
const [errors, data] = await commitRepo.queryCommitsByPattern(commitRepo.getPattern(pattern, args));
const isError = errors === null || errors === void 0 ? void 0 : errors.reduce((pre, cur) => pre || !!cur, false);
return isError
? { status: 'ERROR', message: constants_1.QUERY_ERR, errors }
: { status: 'OK', message: `${data.length} record(s) returned`, data };
};
const doSearch = async ({ repo, kind, query, param, countTotalOnly }) => {
debug('doSearch:kind, %s', kind);
debug('doSearch:kind, %s', query);
const { search, getIndexName } = repo;
const index = getIndexName();
const [errors, count, data] = await search({
countTotalOnly,
kind,
index,
query,
param,
restoreFn: kind === 'entity' && repo.getPostSelector(),
});
debug('doSearch:errors, %O', errors);
const isError = errors === null || errors === void 0 ? void 0 : errors.reduce((pre, cur) => pre || !!cur, false);
return isError
? { status: 'ERROR', message: 'search error', errors }
: {
status: 'OK',
message: `${count} record(s) returned`,
data: countTotalOnly ? count : data,
};
};
return {
clearNotification: async (option) => notificationCenter.clearNotification(option),
clearNotifications: async (option) => notificationCenter.clearNotifications(option),
deleteCommitByEntityId: async ({ entityName, id }) => {
debug('deleteCommitByEntityId:id, %s', id);
if (!entityName || !id)
throw new Error(constants_1.INVALID_ARG);
return deleteItems(commitRepo, commitRepo.getPattern('COMMITS_BY_ENTITYNAME_ENTITYID', [entityName, id]));
},
deleteCommitByEntityName: async ({ entityName }) => {
debug('deleteCommitByEntityName:entityName, %s', entityName);
if (!entityName)
throw new Error(constants_1.INVALID_ARG);
return deleteItems(commitRepo, commitRepo.getPattern('COMMITS_BY_ENTITYNAME', [entityName]));
},
deleteEntityByEntityName: async ({ entityName }) => {
debug('deleteEntityByEntityName:entityName, %s', entityName);
if (!entityName)
throw new Error(constants_1.INVALID_ARG);
const entityRepo = allRepos[entityName];
if (!entityRepo)
throw new Error(`deleteEntityByEntityName: ${entityName} ${constants_1.REPO_NOT_FOUND}`);
return deleteItems(entityRepo, entityRepo.getPattern('ENTITIES_BY_ENTITYNAME', [entityName]));
},
fullTextSearchCommit: async ({ query, param, countTotalOnly }) => {
debug('fullTextSearchCommit:query, %s', query);
debug('fullTextSearchCommit:param, %O', param);
if (!query)
throw new Error(constants_1.INVALID_ARG);
return doSearch({
repo: commitRepo,
countTotalOnly,
kind: 'commit',
query,
param,
});
},
fullTextSearchEntity: async ({ entityName, query, param, countTotalOnly }) => {
debug('fullTextSearchEntity:query, %s', query);
debug('fullTextSearchEntity:param, %O', param);
if (!query || !entityName)
throw new Error(constants_1.INVALID_ARG);
const repo = allRepos[entityName];
if (!repo)
throw new Error(`fullTextSearchEntity: ${entityName} ${constants_1.REPO_NOT_FOUND} -- ${Object.keys(allRepos)}`);
return doSearch({ repo, countTotalOnly, kind: 'entity', query, param });
},
getNotification: async ({ creator, entityName, id, commitId }) => notificationCenter.getNotification({ creator, entityName, id, commitId }),
getNotificationsByFields: async ({ creator, entityName, id }) => notificationCenter.getNotificationsByFields({ creator, entityName, id }),
getRedisCommitRepo: () => commitRepo,
queryCommitByEntityId: async ({ entityName, id }) => {
debug('queryCommitByEntityId:id, %s', id);
if (!entityName || !id)
throw new Error(constants_1.INVALID_ARG);
return queryCommit('COMMITS_BY_ENTITYNAME_ENTITYID', [entityName, id]);
},
queryCommitByEntityName: async ({ entityName }) => {
debug('queryCommitByEntityName:entityName, %s', entityName);
if (!entityName)
throw new Error(constants_1.INVALID_ARG);
return queryCommit('COMMITS_BY_ENTITYNAME', [entityName]);
},
mergeCommit: async ({ commit }) => {
debug('mergeCommit:commit', commit);
if (!utils_1.isCommit(commit))
throw new Error(constants_1.INVALID_ARG);
try {
const key = allRepos['commit'].getKey(commit);
debug('mergeCommit:key, %s', key);
const status = await allRepos['commit'].hmset(commit);
debug('mergeCommit:status, %s', status);
return {
status,
message: `${key} merged successfully`,
data: [key],
};
}
catch (e) {
debug('mergeCommit:"try-catch-error"');
debug('mergeCommit, %O', e);
logger.error(util_1.default.format('mergeCommit - %s, %j', constants_1.REDIS_ERR, e));
throw e;
}
},
mergeCommitBatch: async ({ entityName, commits }) => {
var e_1, _a;
debug('mergeCommitBatch starts');
if (!entityName || !commits)
throw new Error(constants_1.INVALID_ARG);
if (isEqual_1.default(commits, {}))
return {
status: 'OK',
message: constants_1.NO_RECORDS,
data: [],
};
const data = [];
const error = [];
try {
try {
for (var _b = __asyncValues(Object.values(commits)), _c; _c = await _b.next(), !_c.done;) {
const commit = _c.value;
const status = await allRepos['commit'].hmset(commit);
const key = allRepos['commit'].getKey(commit);
if (status === 'OK')
data.push(key);
else
error.push(key);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
}
catch (e) {
debug('mergeCommitBatch:"try-catch-error"');
debug('mergeCommitBatch, %O', e);
logger.error(util_1.default.format('mergeCommitBatch - %s, %j', constants_1.REDIS_ERR, e));
throw e;
}
debug('mergeCommitBatch:error, %O', error);
return {
status: error.length === 0 ? 'OK' : 'ERROR',
message: `${data.length} record(s) merged successfully`,
data,
error,
errors: error,
};
},
mergeEntity: async ({ commit, reducer }) => {
debug('mergeEntity:commit, %O', commit);
const { entityName, entityId, commitId } = commit;
const entityRepo = allRepos[entityName];
const entityKeyInRedis = allRepos[entityName].getKey(commit);
const commitKeyInRedis = allRepos['commit'].getKey(commit);
debug('mergeEntity:entityKeyInRedis, %s', entityKeyInRedis);
debug('mergeEntity:commitKeyInRedis, %s', commitKeyInRedis);
if (!entityRepo)
throw new Error(`${constants_1.FATAL} mergeEntity: ${entityName} ${constants_1.REPO_NOT_FOUND}`);
if (!utils_1.isCommit(commit) || !reducer)
throw new Error(`${constants_1.FATAL} ${constants_1.INVALID_ARG}`);
debug('mergeEntity:step-1');
const pattern = commitRepo.getPattern('COMMITS_BY_ENTITYNAME_ENTITYID', [
entityName,
entityId,
]);
debug('pattern: %s', pattern);
const [errors, restoredCommits] = await commitRepo.queryCommitsByPattern(pattern);
const isError = errors === null || errors === void 0 ? void 0 : errors.reduce((pre, cur) => pre || !!cur, false);
debug('restoredCommits: %O', restoredCommits);
if (isError) {
debug('mergeEntity:errors, %O', errors);
logger.error(util_1.default.format('errors of "await commitRepo.queryCommitsByPattern(pattern)", %j', errors));
return {
status: 'ERROR',
message: `${constants_1.FATAL} fail to retrieve existing commit`,
errors,
};
}
debug('mergeEntity:step-2');
const history = [...restoredCommits, commit];
const { state, reduced } = types_1.computeEntity(history, reducer);
debug('mergeEntity:history, %O', history);
debug('mergeEntity:entity being merged, %O', state);
if (reduced && !(state === null || state === void 0 ? void 0 : state.id)) {
debug('mergeEntity:"fail to reduce"');
logger.error(util_1.default.format('state, %j', state));
return {
status: 'ERROR',
message: constants_1.REDUCE_ERR,
errors: [new Error(`${constants_1.FATAL} fail to reduce, ${entityName}:${entityId}:${commitId}`)],
};
}
const data = [];
try {
let status;
debug('mergeEntity:step-4');
if (reduced) {
status = await allRepos[entityName].hmset(state, history);
data.push({ key: entityKeyInRedis, status });
}
debug('mergeEntity:step-5');
status = await allRepos['commit'].hmset(commit);
data.push({ key: commitKeyInRedis, status });
debug('mergeEntity:step-6');
if (reduced) {
await notificationCenter.notify({
creator: state._creator,
entityName,
id: entityId,
commitId,
});
}
debug('mergeEntity:finish without error');
}
catch (e) {
if (!e.message.startsWith('[lifecycle]'))
logger.error(util_1.default.format('mergeEntity - %s, %j', constants_1.REDIS_ERR, e));
throw e;
}
debug('data returns: %O', data);
return { status: 'OK', message: `${entityKeyInRedis} merged successfully`, data };
},
mergeEntityBatch: async ({ entityName, commits, reducer }) => {
var e_2, _a;
debug('mergeEntityBatch starts');
if (!entityName || !commits || !reducer)
throw new Error(constants_1.INVALID_ARG);
const entityRepo = allRepos[entityName];
if (!entityRepo)
throw new Error(`mergeEntityBatch: ${entityName} ${constants_1.REPO_NOT_FOUND}`);
if (isEqual_1.default(commits, {}))
return {
status: 'OK',
message: constants_1.NO_RECORDS,
data: [],
};
const filteredCommits = filter_1.default(commits, { entityName });
const groupByEntityId = groupBy_1.default(filteredCommits, ({ id }) => id);
const errors = [];
const entities = Object.entries(groupByEntityId)
.map(([entityId, commits]) => {
const { state, reduced } = types_1.computeEntity(commits, reducer);
const keyOfEntityInRedis = allRepos[entityName].getKey(commits[0]);
if (reduced && !state)
errors.push(entityId);
return { state, commits, key: keyOfEntityInRedis };
})
.filter(({ state }) => !!state);
debug('mergeEntityBatch:errors, %O', errors);
const data = [];
try {
for (var entities_1 = __asyncValues(entities), entities_1_1; entities_1_1 = await entities_1.next(), !entities_1_1.done;) {
const { state, commits, key } = entities_1_1.value;
try {
const status = await allRepos[entityName].hmset(state, commits);
data.push({ key, status });
}
catch (e) {
debug('mergeEntityBatch:e, %O', e);
logger.error(util_1.default.format('mergeEntityBatch - %s, %j', constants_1.REDIS_ERR, e));
throw e;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) await _a.call(entities_1);
}
finally { if (e_2) throw e_2.error; }
}
debug('mergeEntityBatch:data, %O', data);
return {
status: errors.length === 0 ? 'OK' : 'ERROR',
message: `${data.length} record(s) merged`,
data,
errors: errors.length === 0 ? null : errors,
};
},
};
};
exports.createQueryDatabase = createQueryDatabase;
//# sourceMappingURL=createQueryDatabase.js.map