UNPKG

@mbc-cqrs-serverless/core

Version:
376 lines 16.1 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var _a; var CommandService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandService = void 0; const common_1 = require("@nestjs/common"); const core_1 = require("@nestjs/core"); const util_1 = require("util"); const constants_1 = require("../constants"); const user_1 = require("../context/user"); const dynamodb_service_1 = require("../data-store/dynamodb.service"); const decorators_1 = require("../decorators"); const helpers_1 = require("../helpers"); const key_1 = require("../helpers/key"); const sns_service_1 = require("../queue/sns.service"); const services_1 = require("../services"); const command_module_definition_1 = require("./command.module-definition"); const data_service_1 = require("./data.service"); const enums_1 = require("./enums"); const data_sync_dds_handler_1 = require("./handlers/data-sync-dds.handler"); const ttl_service_1 = require("./ttl.service"); const TABLE_NAME = Symbol('command'); const DATA_SYNC_HANDLER = Symbol(decorators_1.DATA_SYNC_HANDLER_METADATA); let CommandService = CommandService_1 = class CommandService { constructor(options, dynamoDbService, explorerService, moduleRef, snsService, dataSyncDdsHandler, dataService, ttlService) { this.options = options; this.dynamoDbService = dynamoDbService; this.explorerService = explorerService; this.moduleRef = moduleRef; this.snsService = snsService; this.dataSyncDdsHandler = dataSyncDdsHandler; this.dataService = dataService; this.ttlService = ttlService; this[_a] = []; this.tableName = this.dynamoDbService.getTableName(this.options.tableName, enums_1.TableType.COMMAND); this.logger = new common_1.Logger(`${CommandService_1.name}:${this.tableName}`); } publishItem(key) { throw new Error('Method not implemented.'); } onModuleInit() { if (!this.options.disableDefaultHandler) { this[DATA_SYNC_HANDLER] = [this.dataSyncDdsHandler]; } if (this.options.dataSyncHandlers?.length) { // this.logger.debug('init data sync handlers') this[DATA_SYNC_HANDLER].push(...this.options.dataSyncHandlers.map((HandlerClass) => this.moduleRef.get(HandlerClass, { strict: false }))); } this.logger.debug('find data sync handlers from decorator'); const { dataSyncHandlers } = this.explorerService.exploreDataSyncHandlers(this.options.tableName); this[DATA_SYNC_HANDLER].push(...dataSyncHandlers .map((handler) => this.moduleRef.get(handler, { strict: false })) .filter((handler) => !!handler)); // this.logger.debug( // 'data sync handlers length: ' + this[DATA_SYNC_HANDLER].length, // ) } set tableName(name) { this[TABLE_NAME] = name; } get tableName() { return this[TABLE_NAME]; } get dataSyncHandlers() { // this.logger.debug( // 'dataSyncHandlers size:: ' + this[DATA_SYNC_HANDLER].length, // ) return this[DATA_SYNC_HANDLER]; } getDataSyncHandler(name) { return this.dataSyncHandlers.find((handler) => handler.constructor.name === name); } async publishPartialUpdateSync(input, options) { const item = await this.dataService.getItem({ pk: input.pk, sk: input.sk, }); if (!item || item.version !== input.version) { throw new common_1.BadRequestException('The input is not a valid, item not found or version not match'); } if (!Object.keys(input).includes('ttl')) { delete item.ttl; } const fullInput = (0, helpers_1.mergeDeep)({}, item, input, { version: item.version }); this.logger.debug('publishPartialUpdateSync::', fullInput); return await this.publishSync(fullInput, options); } async publishPartialUpdateAsync(input, options) { let item; if (input.version > constants_1.VERSION_FIRST) { item = await this.getItem({ pk: input.pk, sk: (0, key_1.addSortKeyVersion)(input.sk, input.version), }); } else { item = await this.getLatestItem({ pk: input.pk, sk: (0, key_1.removeSortKeyVersion)(input.sk), }); } if (!item) { throw new common_1.BadRequestException('The input key is not a valid, item not found'); } if (!Object.keys(input).includes('ttl')) { delete item.ttl; } const fullInput = (0, helpers_1.mergeDeep)({}, item, input, { version: item.version }); this.logger.debug('publishPartialUpdate::', fullInput); return await this.publishAsync(fullInput, options); } /** * @deprecated Use {@link publishPartialUpdateAsync} instead. * This function is outdated and will be removed in the next major release. */ async publishPartialUpdate(input, options) { let item; if (input.version > constants_1.VERSION_FIRST) { item = await this.getItem({ pk: input.pk, sk: (0, key_1.addSortKeyVersion)(input.sk, input.version), }); } else { item = await this.getLatestItem({ pk: input.pk, sk: (0, key_1.removeSortKeyVersion)(input.sk), }); } if (!item) { throw new common_1.BadRequestException('The input key is not a valid, item not found'); } if (!Object.keys(input).includes('ttl')) { delete item.ttl; } const fullInput = (0, helpers_1.mergeDeep)({}, item, input, { version: item.version }); this.logger.debug('publishPartialUpdate::', fullInput); return await this.publish(fullInput, options); } async publishSync(input, options) { const item = await this.dataService.getItem({ pk: input.pk, sk: input.sk }); let inputVersion = input.version ?? constants_1.VERSION_FIRST; if (inputVersion === constants_1.VERSION_LATEST) { inputVersion = item?.version ?? constants_1.VERSION_FIRST; } else if ((item?.version ?? 0) !== inputVersion) { throw new common_1.BadRequestException('Invalid input version. The input version must be equal to the latest version'); } const userContext = (0, user_1.getUserContext)(options.invokeContext); const requestId = options?.requestId || options.invokeContext?.context?.awsRequestId; const sourceIp = options.invokeContext?.event?.requestContext?.http?.sourceIp; const version = (item?.version ?? inputVersion) + 1; const command = { ttl: await this.ttlService.calculateTtl(enums_1.TableType.DATA, (0, key_1.getTenantCode)(input.pk)), ...input, version, source: options?.source, requestId, createdAt: new Date(), updatedAt: item?.updatedAt ?? new Date(), createdBy: userContext.userId, updatedBy: item?.updatedBy ?? userContext.userId, createdIp: sourceIp, updatedIp: item?.updatedIp ?? sourceIp, }; this.logger.debug('publishSync::', command); await this.dataService.publish(command); const targetSyncHandlers = this.dataSyncHandlers?.filter((handler) => handler.type !== 'dynamodb'); await Promise.all(targetSyncHandlers.map((handler) => handler.up(command))); return command; } async publishAsync(input, options) { let inputVersion = input.version || constants_1.VERSION_FIRST; let item; if (inputVersion === constants_1.VERSION_LATEST) { item = await this.getLatestItem({ pk: input.pk, sk: (0, key_1.removeSortKeyVersion)(input.sk), }); inputVersion = item?.version || constants_1.VERSION_FIRST; } else if (inputVersion > constants_1.VERSION_FIRST) { // check current version item = await this.getItem({ pk: input.pk, sk: (0, key_1.addSortKeyVersion)(input.sk, inputVersion), }); if (!item) { throw new common_1.BadRequestException('Invalid input version. The input version must be equal to the latest version'); } } if (item && this.isNotCommandDirty(item, input)) { // do not update if command is not dirty return null; } const userContext = (0, user_1.getUserContext)(options.invokeContext); const requestId = options?.requestId || options.invokeContext?.context?.awsRequestId; const sourceIp = options.invokeContext?.event?.requestContext?.http?.sourceIp; const version = inputVersion + 1; const command = { ttl: await this.ttlService.calculateTtl(enums_1.TableType.DATA, (0, key_1.getTenantCode)(input.pk)), ...input, sk: (0, key_1.addSortKeyVersion)(input.sk, version), version, source: options?.source, requestId, createdAt: new Date(), updatedAt: new Date(), createdBy: userContext.userId, updatedBy: userContext.userId, createdIp: sourceIp, updatedIp: sourceIp, }; this.logger.debug('publish::', command); await this.dynamoDbService.putItem(this.tableName, command, 'attribute_not_exists(pk) AND attribute_not_exists(sk)'); return command; } /** * @deprecated Use {@link publishAsync} instead. * This function is outdated and will be removed in the next major release. */ async publish(input, options) { return await this.publishAsync(input, options); } async duplicate(key, options) { const item = await this.getItem(key); if (!item) { throw new common_1.BadRequestException('The input key is not a valid, item not found'); } const userContext = (0, user_1.getUserContext)(options.invokeContext); item.version += 1; item.sk = (0, key_1.addSortKeyVersion)(item.sk, item.version); item.source = 'duplicated'; item.requestId = options?.requestId || options.invokeContext?.context?.awsRequestId; item.updatedAt = new Date(); item.updatedBy = userContext.userId; item.updatedIp = options.invokeContext?.event?.requestContext?.http?.sourceIp; this.logger.debug('duplicate::', item); await this.dynamoDbService.putItem(this.tableName, item, 'attribute_not_exists(pk) AND attribute_not_exists(sk)'); return item; } async reSyncData() { const targetSyncHandlers = this.dataSyncHandlers?.filter((handler) => handler.type !== 'dynamodb'); if (!targetSyncHandlers?.length) { this.logger.debug('no data sync handlers'); return; } const dataTableName = this.dataService.tableName; let startKey = undefined; while (true) { const res = await this.dynamoDbService.listAllItems(dataTableName, startKey); if (res?.items?.length) { for (const item of res.items) { item.sk = (0, key_1.addSortKeyVersion)(item.sk, item.version); for (const handler of targetSyncHandlers) { await handler.up(item); } } } startKey = res?.lastKey; if (!startKey) { break; } } } async updateStatus(key, status, notifyId) { await this.dynamoDbService.updateItem(this.tableName, key, { set: { status }, }); // notification via SNS await this.snsService.publish({ action: 'command-status', ...key, table: this.tableName, id: notifyId || `${this.tableName}#${key.pk}#${key.sk}`, tenantCode: key.pk.substring(key.pk.indexOf('#') + 1), content: { status }, }); } async getItem(key) { if (!key.sk.includes(constants_1.VER_SEPARATOR)) { return this.getLatestItem(key); } return await this.dynamoDbService.getItem(this.tableName, key); } async getLatestItem(key) { const lookUpStep = 5; const dataItem = await this.dataService.getItem(key); let ver = (dataItem?.version || 0) + lookUpStep; let isUp = true; while (true) { if (ver <= constants_1.VERSION_FIRST) { return null; } const item = await this.getItem({ pk: key.pk, sk: (0, key_1.addSortKeyVersion)(key.sk, ver), }); if (item) { if (!isUp) { // look down return item; } // continue look up ver += lookUpStep; } else { // look down ver -= 1; isUp = false; } } } isNotCommandDirty(item, input) { const comparedKeys = [ 'id', 'code', 'name', 'tenantCode', 'type', 'isDeleted', 'seq', 'ttl', 'attributes', ]; return (0, util_1.isDeepStrictEqual)(structuredClone((0, helpers_1.pickKeys)(item, comparedKeys)), structuredClone((0, helpers_1.pickKeys)(input, comparedKeys))); } async updateTtl(key) { const version = (0, key_1.getSortKeyVersion)(key.sk); const sk = (0, key_1.removeSortKeyVersion)(key.sk); if (version <= constants_1.VERSION_FIRST + 1) { return null; } const previousSk = (0, key_1.addSortKeyVersion)(sk, version - 1); const command = await this.dynamoDbService.getItem(this.tableName, { pk: key.pk, sk: previousSk, }); if (!command) { return null; } command.sk = previousSk; const ttl = await this.ttlService.calculateTtl(enums_1.TableType.COMMAND, (0, key_1.getTenantCode)(key.pk)); command.ttl = ttl; this.logger.debug('updateTtl::', command); return await this.dynamoDbService.putItem(this.tableName, command); } }; exports.CommandService = CommandService; _a = DATA_SYNC_HANDLER; exports.CommandService = CommandService = CommandService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, common_1.Inject)(command_module_definition_1.MODULE_OPTIONS_TOKEN)), __metadata("design:paramtypes", [Object, dynamodb_service_1.DynamoDbService, services_1.ExplorerService, core_1.ModuleRef, sns_service_1.SnsService, data_sync_dds_handler_1.DataSyncDdsHandler, data_service_1.DataService, ttl_service_1.TtlService]) ], CommandService); //# sourceMappingURL=command.service.js.map