@mbc-cqrs-serverless/task
Version:
long-running task
239 lines • 10.5 kB
JavaScript
;
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 TaskService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskService = void 0;
const core_1 = require("@mbc-cqrs-serverless/core");
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const ulid_1 = require("ulid");
const task_entity_1 = require("./entity/task.entity");
const task_list_entity_1 = require("./entity/task-list.entity");
const enums_1 = require("./enums");
const status_enum_1 = require("./enums/status.enum");
const event_1 = require("./event");
let TaskService = TaskService_1 = class TaskService {
constructor(dynamoDbService, snsService, config) {
this.dynamoDbService = dynamoDbService;
this.snsService = snsService;
this.config = config;
this.logger = new common_1.Logger(TaskService_1.name);
this.tableName = dynamoDbService.getTableName('tasks');
this.alarmTopicArn = this.config.get('SNS_ALARM_TOPIC_ARN');
}
async createTask(dto, options) {
const sourceIp = options.invokeContext?.event?.requestContext?.http?.sourceIp;
const userContext = (0, core_1.getUserContext)(options.invokeContext);
const taskCode = (0, ulid_1.ulid)();
const pk = `TASK${core_1.KEY_SEPARATOR}${dto.tenantCode}`;
const sk = `${dto.taskType}${core_1.KEY_SEPARATOR}${taskCode}`;
const item = {
id: `${pk}${core_1.KEY_SEPARATOR}${sk}`,
pk,
sk,
version: 0,
code: taskCode,
type: dto.taskType,
name: dto.name || dto.taskType,
tenantCode: dto.tenantCode,
status: status_enum_1.TaskStatusEnum.CREATED,
input: dto.input,
requestId: options.invokeContext?.context?.awsRequestId,
createdAt: new Date(),
updatedAt: new Date(),
createdBy: userContext.userId,
updatedBy: userContext.userId,
createdIp: sourceIp,
updatedIp: sourceIp,
};
await this.dynamoDbService.putItem(this.tableName, item);
return new task_entity_1.TaskEntity(item);
}
async createStepFunctionTask(dto, options) {
const sourceIp = options.invokeContext?.event?.requestContext?.http?.sourceIp;
const userContext = (0, core_1.getUserContext)(options.invokeContext);
const taskCode = (0, ulid_1.ulid)();
const pk = `${enums_1.TaskTypesEnum.SFN_TASK}${core_1.KEY_SEPARATOR}${dto.tenantCode}`;
const sk = `${dto.taskType}${core_1.KEY_SEPARATOR}${taskCode}`;
const item = {
id: `${pk}${core_1.KEY_SEPARATOR}${sk}`,
pk,
sk,
version: 0,
code: taskCode,
type: dto.taskType,
name: dto.name || dto.taskType,
tenantCode: dto.tenantCode,
status: status_enum_1.TaskStatusEnum.CREATED,
input: dto.input,
requestId: options.invokeContext?.context?.awsRequestId,
createdAt: new Date(),
updatedAt: new Date(),
createdBy: userContext.userId,
updatedBy: userContext.userId,
createdIp: sourceIp,
updatedIp: sourceIp,
};
await this.dynamoDbService.putItem(this.tableName, item);
return new task_entity_1.TaskEntity(item);
}
async createSubTask(event) {
const subTasks = [];
await Promise.all(event.taskEvent.taskEntity.input.map((input, index) => {
const pk = event.taskEvent.taskKey.pk;
const sk = `${event.taskEvent.taskKey.sk}${core_1.KEY_SEPARATOR}${index}`;
const taskCode = (0, ulid_1.ulid)();
const item = new task_entity_1.TaskEntity({
id: `${pk}${core_1.KEY_SEPARATOR}${sk}`,
pk,
sk,
version: 0,
code: taskCode,
type: event.taskEvent.taskEntity.type,
name: event.taskEvent.taskEntity.name,
tenantCode: event.taskEvent.taskEntity.tenantCode,
status: status_enum_1.TaskStatusEnum.CREATED,
input,
requestId: event.taskEvent.taskEntity.requestId,
createdAt: new Date(),
updatedAt: new Date(),
createdBy: event.taskEvent.taskEntity.createdBy,
updatedBy: event.taskEvent.taskEntity.updatedBy,
createdIp: event.taskEvent.taskEntity.createdIp,
updatedIp: event.taskEvent.taskEntity.updatedIp,
});
subTasks.push(item);
return this.dynamoDbService.putItem(this.tableName, item);
}));
return subTasks;
}
async getAllSubTask(subTask) {
const parentKey = subTask.sk
.split(core_1.KEY_SEPARATOR)
.slice(0, -1)
.join(core_1.KEY_SEPARATOR);
this.logger.debug('TaskService getAllSubTask parentKey:', parentKey);
const allItems = [];
let lastSk = undefined;
do {
const res = await this.dynamoDbService.listItemsByPk(this.tableName, subTask.pk, {
skExpression: 'begins_with(sk, :typeCode)',
skAttributeValues: {
':typeCode': `${parentKey}${core_1.KEY_SEPARATOR}`,
},
}, lastSk);
allItems.push(...(res?.items || []).map((item) => new task_entity_1.TaskEntity(item)));
lastSk = res.lastSk;
this.logger.debug('TaskService getAllSubTask lastSk:', lastSk);
} while (lastSk);
return allItems;
}
async updateStepFunctionTask(key, attributes, status, notifyId) {
await this.dynamoDbService.updateItem(this.tableName, key, {
set: { attributes, status },
});
// notification via SNS
await this.snsService.publish({
action: 'task-status',
...key,
table: this.tableName,
id: notifyId || `${key.pk}#${key.sk}`,
tenantCode: key.pk.substring(key.pk.indexOf('#') + 1),
content: { attributes, status },
});
}
async getTask(key) {
const item = await this.dynamoDbService.getItem(this.tableName, key);
return new task_entity_1.TaskEntity(item);
}
async updateStatus(key, status, attributes, notifyId) {
await this.dynamoDbService.updateItem(this.tableName, key, {
set: { status, attributes },
});
// notification via SNS
await this.snsService.publish({
action: 'task-status',
...key,
table: this.tableName,
id: notifyId || `${key.pk}#${key.sk}`,
tenantCode: key.pk.substring(key.pk.indexOf('#') + 1),
content: { status, attributes },
});
}
async updateSubTaskStatus(key, status, attributes, notifyId) {
await this.dynamoDbService.updateItem(this.tableName, key, {
set: { status, attributes },
});
// notification via SNS -> insert to queue
await this.snsService.publish({
action: 'sub-task-status',
...key,
table: this.tableName,
id: notifyId || `${key.pk}#${key.sk}`,
tenantCode: key.pk.substring(key.pk.indexOf('#') + 1),
content: { status, attributes },
});
}
async listItemsByPk(tenantCode, type, options) {
if (!['TASK', 'SFN_TASK'].includes(type)) {
throw new common_1.NotFoundException(`The type of task, must be either "TASK" or "SFN_TASK"`);
}
const pk = `${type}${core_1.KEY_SEPARATOR}${tenantCode}`;
const { lastSk, items } = await this.dynamoDbService.listItemsByPk(this.tableName, pk, options?.sk, options?.startFromSk, options?.limit, options?.order);
return new task_list_entity_1.TaskListEntity({
lastSk,
items: items.map((item) => new task_entity_1.TaskEntity(item)),
});
}
async publishAlarm(event, errorDetails) {
this.logger.debug('event', event);
const taskKey = event instanceof event_1.TaskQueueEvent ? event.taskEvent.taskKey : event.taskKey;
const tenantCode = taskKey.pk.substring(taskKey.pk.indexOf(core_1.KEY_SEPARATOR) + 1);
const alarm = {
action: 'sfn-alarm',
id: `${taskKey.pk}#${taskKey.sk}`,
table: this.tableName,
pk: taskKey.pk,
sk: taskKey.sk,
tenantCode,
content: {
errorMessage: errorDetails,
},
};
this.logger.error('alarm:::', alarm);
await this.snsService.publish(alarm, this.alarmTopicArn);
}
async formatTaskStatus(tasks) {
const result = {
subTaskCount: tasks.length,
subTaskSucceedCount: this.countTaskStatus(tasks, status_enum_1.TaskStatusEnum.COMPLETED),
subTaskFailedCount: this.countTaskStatus(tasks, status_enum_1.TaskStatusEnum.FAILED),
subTaskRunningCount: this.countTaskStatus(tasks, status_enum_1.TaskStatusEnum.PROCESSING),
subTasks: tasks.map((task) => ({
pk: task.pk,
sk: task.sk,
status: task.status,
})),
};
return result;
}
countTaskStatus(tasks, status) {
return tasks.filter((task) => task.status === status).length;
}
};
exports.TaskService = TaskService;
exports.TaskService = TaskService = TaskService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [core_1.DynamoDbService,
core_1.SnsService,
config_1.ConfigService])
], TaskService);
//# sourceMappingURL=task.service.js.map