@chevre/domain
Version:
Chevre Domain Library for Node.js
1,071 lines (1,070 loc) • 46.2 kB
JavaScript
"use strict";
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 __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskRepo = void 0;
const moment = require("moment");
const errorHandler_1 = require("../errorHandler");
const task_1 = require("../eventEmitter/task");
const factory = require("../factory");
const settings_1 = require("../settings");
const task_2 = require("./mongoose/schemas/task");
/**
* タスク実行時のソート条件
*/
const sortOrder4executionOfTasks = {
numberOfTried: factory.sortType.Ascending, // トライ回数の少なさ優先
runsAt: factory.sortType.Ascending // 実行予定日時の早さ優先
};
const executableTaskProjection = {
_id: 0,
id: { $toString: '$_id' },
// 必要最低限にprojection(2024-04-23~)
data: 1,
name: 1,
status: 1,
numberOfTried: 1,
project: 1,
remainingNumberOfTries: 1,
runsAt: 1,
expires: 1
};
// type IProjection = { [key in IKeyOfProjection]?: 0 | 1; };
const AVAILABLE_PROJECT_FIELDS = [
'alternateName',
'identifier',
'description',
'project',
'name',
'status',
'runsAt',
'remainingNumberOfTries',
'lastTriedAt',
'numberOfTried',
'executionResults',
'executor',
'data',
'dateAborted',
'expires'
];
/**
* タスクリポジトリ
*/
class TaskRepo {
constructor(connection) {
this.taskModel = connection.model(task_2.modelName, (0, task_2.createSchema)());
}
// tslint:disable-next-line:cyclomatic-complexity max-func-body-length
static CREATE_MONGO_CONDITIONS(params) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
const andConditions = [];
const idEq = (_a = params.id) === null || _a === void 0 ? void 0 : _a.$eq;
if (typeof idEq === 'string') {
andConditions.push({ _id: { $eq: idEq } });
}
const projectIdEq = (_c = (_b = params.project) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.$eq;
if (typeof projectIdEq === 'string') {
andConditions.push({ 'project.id': { $eq: projectIdEq } });
}
if (typeof params.name === 'string') {
andConditions.push({ name: { $eq: params.name } });
}
else {
const nameIn = (_d = params.name) === null || _d === void 0 ? void 0 : _d.$in;
if (Array.isArray(nameIn)) {
andConditions.push({ name: { $in: nameIn } });
}
const nameNin = (_e = params.name) === null || _e === void 0 ? void 0 : _e.$nin;
if (Array.isArray(nameNin)) {
andConditions.push({ name: { $nin: nameNin } });
}
}
const statusEq = (_f = params.status) === null || _f === void 0 ? void 0 : _f.$eq;
if (typeof statusEq === 'string') {
andConditions.push({ status: { $eq: statusEq } });
}
if (Array.isArray(params.statuses)) {
andConditions.push({ status: { $in: params.statuses } });
}
if (params.runsFrom instanceof Date) {
andConditions.push({ runsAt: { $gte: params.runsFrom } });
}
if (params.runsThrough instanceof Date) {
andConditions.push({ runsAt: { $lte: params.runsThrough } });
}
if (params.lastTriedFrom instanceof Date) {
andConditions.push({ lastTriedAt: { $type: 'date', $gte: params.lastTriedFrom } });
}
if (params.lastTriedThrough instanceof Date) {
andConditions.push({ lastTriedAt: { $type: 'date', $lte: params.lastTriedThrough } });
}
const dateAbortedGte = (_g = params.dateAborted) === null || _g === void 0 ? void 0 : _g.$gte;
if (dateAbortedGte instanceof Date) {
andConditions.push({ dateAborted: { $type: 'date', $gte: dateAbortedGte } });
}
const dateAbortedLte = (_h = params.dateAborted) === null || _h === void 0 ? void 0 : _h.$lte;
if (dateAbortedLte instanceof Date) {
andConditions.push({ dateAborted: { $type: 'date', $lte: dateAbortedLte } });
}
const objectIdEq = (_l = (_k = (_j = params.data) === null || _j === void 0 ? void 0 : _j.object) === null || _k === void 0 ? void 0 : _k.id) === null || _l === void 0 ? void 0 : _l.$eq;
if (typeof objectIdEq === 'string') {
andConditions.push({ 'data.object.id': { $exists: true, $eq: objectIdEq } });
}
const objectOrderNumberEq = (_p = (_o = (_m = params.data) === null || _m === void 0 ? void 0 : _m.object) === null || _o === void 0 ? void 0 : _o.orderNumber) === null || _p === void 0 ? void 0 : _p.$eq;
if (typeof objectOrderNumberEq === 'string') {
andConditions.push({ 'data.object.orderNumber': { $exists: true, $eq: objectOrderNumberEq } });
}
const objectTransactionNumberEq = (_s = (_r = (_q = params.data) === null || _q === void 0 ? void 0 : _q.object) === null || _r === void 0 ? void 0 : _r.transactionNumber) === null || _s === void 0 ? void 0 : _s.$eq;
if (typeof objectTransactionNumberEq === 'string') {
andConditions.push({ 'data.object.transactionNumber': { $exists: true, $eq: objectTransactionNumberEq } });
}
const objectPurposeIdEq = (_v = (_u = (_t = params.data) === null || _t === void 0 ? void 0 : _t.purpose) === null || _u === void 0 ? void 0 : _u.id) === null || _v === void 0 ? void 0 : _v.$eq;
if (typeof objectPurposeIdEq === 'string') {
andConditions.push({ 'data.purpose.id': { $exists: true, $eq: objectPurposeIdEq } });
}
const objectPurposeOrderNumberEq = (_y = (_x = (_w = params.data) === null || _w === void 0 ? void 0 : _w.purpose) === null || _x === void 0 ? void 0 : _x.orderNumber) === null || _y === void 0 ? void 0 : _y.$eq;
if (typeof objectPurposeOrderNumberEq === 'string') {
andConditions.push({ 'data.purpose.orderNumber': { $exists: true, $eq: objectPurposeOrderNumberEq } });
}
const alternateNameRegex = (_z = params.alternateName) === null || _z === void 0 ? void 0 : _z.$regex;
if (typeof alternateNameRegex === 'string' && alternateNameRegex.length > 0) {
andConditions.push({ alternateName: { $exists: true, $regex: new RegExp(alternateNameRegex) } });
}
const identifierRegex = (_0 = params.identifier) === null || _0 === void 0 ? void 0 : _0.$regex;
if (typeof identifierRegex === 'string' && identifierRegex.length > 0) {
andConditions.push({ identifier: { $exists: true, $regex: new RegExp(identifierRegex) } });
}
return andConditions;
}
runImmediately(
// resolve uniqueness of identifier(2025-03-27~)
params,
// support customr function(2025-05-25~)
next) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const { expires } = params;
if (!(expires instanceof Date)) {
throw new factory.errors.Argument('expires', 'must be Date');
}
const savingTask = Object.assign(Object.assign({}, params), { status: factory.taskStatus.Ready, numberOfTried: 0, executionResults: [] });
const result = yield this.taskModel.insertMany(savingTask, { rawResult: true });
const id = (_b = (_a = result.insertedIds) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.toHexString();
if (typeof id !== 'string') {
throw new factory.errors.Internal('task not saved');
}
task_1.taskEventEmitter.emitTaskStatusChanged({
id,
status: factory.taskStatus.Ready,
expires // emit expires(2025-03-31~)
}, next);
return { id };
});
}
saveMany(taskAttributes, options) {
return __awaiter(this, void 0, void 0, function* () {
const emitImmediately = (options === null || options === void 0 ? void 0 : options.emitImmediately) === true;
if (taskAttributes.length > 0) {
// resolve uniqueness of identifier(2025-03-27~)
const creatingTasks = taskAttributes.map((_a) => {
var { identifier } = _a, creatingTask = __rest(_a, ["identifier"]);
return creatingTask;
});
const result = yield this.taskModel.insertMany(creatingTasks, { ordered: false, rawResult: true });
if (result.insertedCount !== taskAttributes.length) {
throw new factory.errors.Internal('all tasks not saved');
}
const savedTasks = Object.values(result.insertedIds)
.map((objectId) => {
return { id: objectId.toHexString() };
});
if (emitImmediately) {
savedTasks.forEach((savedTask) => {
task_1.taskEventEmitter.emitTaskStatusChanged({
id: savedTask.id,
status: factory.taskStatus.Ready
});
});
// taskAttributes.forEach((savedTask) => {
// taskEventEmitter.emitTaskStatusChanged({
// name: savedTask.name,
// status: factory.taskStatus.Ready
// });
// });
}
// return result.ops;
return savedTasks;
}
else {
return [];
}
});
}
/**
* タスク識別子から検索する
*/
findByIdentifier(params) {
return __awaiter(this, void 0, void 0, function* () {
const projection = {
_id: 0,
id: { $toString: '$_id' },
status: 1
};
const doc = yield this.taskModel.findOne({
'project.id': { $eq: params.project.id },
name: { $eq: params.name },
identifier: { $exists: true, $eq: params.identifier }
}, projection)
.lean() // lean(2024-09-26~)
.exec();
if (doc === null) {
return;
}
const { id, status } = doc;
return { id, status };
});
}
// public async createIfNotExistByIdentifier(
// params: factory.task.IAttributes<factory.taskName> & {
// // resolve uniqueness of identifier(2025-03-27~)
// identifier: string;
// },
// options: IOptionOnCreate
// ): Promise<void> {
// if (typeof params.identifier !== 'string' || params.identifier.length === 0) {
// throw new factory.errors.ArgumentNull('identifier');
// }
// try {
// const createdTask = await this.taskModel.findOneAndUpdate(
// {
// 'project.id': { $eq: params.project.id },
// name: { $eq: params.name },
// identifier: { $exists: true, $eq: params.identifier }
// },
// { $setOnInsert: params },
// { new: true, upsert: true }
// )
// .select({ _id: 1 })
// .exec();
// if (options.emitImmediately) {
// taskEventEmitter.emitTaskStatusChanged({
// id: createdTask.id,
// name: params.name,
// status: factory.taskStatus.Ready
// });
// }
// } catch (error) {
// let throwsError = true;
// if (await isMongoError(error)) {
// // すでにidentifierが存在する場合ok
// if (error.code === MongoErrorCode.DuplicateKey) {
// throwsError = false;
// }
// }
// if (throwsError) {
// throw error;
// }
// }
// }
/**
* タスク識別子から冪等作成する
* reimplement createIfNotExistByIdentifier(2025-03-28~)
*/
createIfNotExistByAlternateName(params, options) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof params.alternateName !== 'string' || params.alternateName.length === 0) {
throw new factory.errors.ArgumentNull('alternateName');
}
let createdTask;
const filterQuery = {
'project.id': { $eq: params.project.id },
name: { $eq: params.name },
alternateName: { $exists: true, $eq: params.alternateName }
};
const projection = {
_id: 0,
id: { $toString: '$_id' }
};
try {
createdTask = yield this.taskModel.findOneAndUpdate(filterQuery, { $setOnInsert: params }, {
new: true,
upsert: true,
projection
})
.lean()
.exec();
}
catch (error) {
let throwsError = true;
if (yield (0, errorHandler_1.isMongoError)(error)) {
// すでにalternateNameが存在する場合ok
if (error.code === errorHandler_1.MongoErrorCode.DuplicateKey) {
throwsError = false;
createdTask = yield this.taskModel.findOne(filterQuery, projection)
.lean()
.exec();
}
}
if (throwsError) {
throw error;
}
}
if (typeof (createdTask === null || createdTask === void 0 ? void 0 : createdTask.id) === 'string') {
if (options.emitImmediately) {
task_1.taskEventEmitter.emitTaskStatusChanged({
id: createdTask.id,
name: params.name,
status: factory.taskStatus.Ready
});
}
}
else {
throw new factory.errors.Internal(`falied in creating a task unexpectedly. ${params.alternateName}`);
}
});
}
// public async createInformTaskIfNotExist(
// // resolve uniqueness of identifier(2025-03-27~)
// params: Pick<
// factory.task.IAttributes<factory.taskName.TriggerWebhook>,
// 'data' | 'executionResults' | 'name' | 'numberOfTried' | 'project' | 'remainingNumberOfTries' | 'runsAt' | 'status'
// > & {
// data: factory.task.triggerWebhook.IInformAnyResourceAction & {
// object: factory.notification.person.IPersonAsNotification;
// };
// },
// options: IOptionOnCreate
// ): Promise<void> {
// const createdTask = await this.taskModel.findOneAndUpdate(
// {
// 'project.id': { $eq: params.project.id },
// name: params.name,
// 'data.object.id': {
// $exists: true,
// $eq: String(params.data.object.id)
// }
// },
// { $setOnInsert: params },
// { new: true, upsert: true }
// )
// .select({ _id: 1 })
// .exec();
// if (options.emitImmediately) {
// taskEventEmitter.emitTaskStatusChanged({
// id: createdTask.id,
// name: params.name,
// status: factory.taskStatus.Ready
// });
// }
// }
/**
* 取引削除タスク冪等作成
*/
createDeleteTransactionTaskIfNotExist(
// resolve uniqueness of identifier(2025-03-27~)
params, options) {
return __awaiter(this, void 0, void 0, function* () {
if (params.data.object.specifyingMethod !== factory.task.deleteTransaction.SpecifyingMethod.Id) {
throw new factory.errors.NotImplemented(`only ${factory.task.deleteTransaction.SpecifyingMethod.Id} implemented`);
}
const createdTask = yield this.taskModel.findOneAndUpdate({
'project.id': { $eq: params.project.id },
name: { $eq: params.name },
'data.object.id': { $exists: true, $eq: params.data.object.id }
}, { $setOnInsert: params }, { new: true, upsert: true })
.select({ _id: 1 })
.exec();
if (options.emitImmediately) {
task_1.taskEventEmitter.emitTaskStatusChanged({
id: createdTask.id,
name: params.name,
status: factory.taskStatus.Ready
});
}
});
}
// public async createConfirmReserveTransactionTaskIfNotExist(
// params: factory.task.IAttributes<factory.taskName.ConfirmReserveTransaction>,
// options: IOptionOnCreate
// ): Promise<void> {
// const createdTask = await this.taskModel.findOneAndUpdate(
// {
// 'project.id': { $eq: params.project.id },
// name: { $eq: params.name },
// 'data.object.transactionNumber': {
// $exists: true,
// $eq: String(params.data.object.transactionNumber)
// },
// 'data.purpose.orderNumber': {
// $exists: true,
// $eq: String(params.data.purpose.orderNumber)
// }
// },
// { $setOnInsert: params },
// { new: true, upsert: true }
// )
// .select({ _id: 1 })
// .exec();
// if (options.emitImmediately) {
// taskEventEmitter.emitTaskStatusChanged({
// id: createdTask.id,
// name: params.name,
// status: factory.taskStatus.Ready
// });
// }
// }
createOnAssetTransactionStatusChangedTaskIfNotExist(
// resolve uniqueness of identifier(2025-03-27~)
params, options) {
return __awaiter(this, void 0, void 0, function* () {
const createdTask = yield this.taskModel.findOneAndUpdate({
'project.id': { $eq: params.project.id },
name: { $eq: params.name },
'data.object.transactionNumber': {
$exists: true,
$eq: String(params.data.object.transactionNumber)
},
'data.purpose.orderNumber': {
$exists: true,
$eq: String(params.data.purpose.orderNumber)
}
}, { $setOnInsert: params }, { new: true, upsert: true })
.select({ _id: 1 })
.exec();
if (options.emitImmediately) {
task_1.taskEventEmitter.emitTaskStatusChanged({
id: createdTask.id,
name: params.name,
status: factory.taskStatus.Ready
});
}
});
}
executeOneById(params) {
return __awaiter(this, void 0, void 0, function* () {
const now = new Date();
let doc;
if (params.status === factory.taskStatus.Running) {
doc = yield this.taskModel.findOne(Object.assign({ _id: { $eq: params.id }, status: { $eq: params.status }, runsAt: { $lt: now } }, (params.expires instanceof Date) ? { expires: { $gt: now } } : undefined), executableTaskProjection)
.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.lean()
.exec();
}
else {
doc = yield this.taskModel.findOneAndUpdate(Object.assign({ _id: { $eq: params.id }, status: { $eq: params.status }, runsAt: { $lt: now } }, (params.expires instanceof Date) ? { expires: { $gt: now } } : undefined), {
$set: {
status: factory.taskStatus.Running, // 実行中に変更
lastTriedAt: now,
executor: params.executor
},
$inc: {
remainingNumberOfTries: -1, // 残りトライ可能回数減らす
numberOfTried: 1 // トライ回数増やす
}
}, { new: true, projection: executableTaskProjection })
.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.lean() // lean(2024-09-26~)
.exec();
}
if (doc === null) {
// tslint:disable-next-line:no-null-keyword
return null;
}
return doc;
});
}
/**
* support no name(2025-03-04~)
*/
executeOneIfExists(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!(params.runsAt.$lt instanceof Date)) {
throw new factory.errors.Argument('runsAt.$lt', 'must be Date');
}
const nameEq = (_a = params.name) === null || _a === void 0 ? void 0 : _a.$eq;
const nameNin = (_b = params.name) === null || _b === void 0 ? void 0 : _b.$nin;
const doc = yield this.taskModel.findOneAndUpdate(Object.assign({ status: { $eq: factory.taskStatus.Ready }, runsAt: { $lt: params.runsAt.$lt } }, (typeof nameEq === 'string' || Array.isArray(nameNin))
? {
name: Object.assign(Object.assign({}, (typeof nameEq === 'string') ? { $eq: nameEq } : undefined), (Array.isArray(nameNin)) ? { $nin: nameNin } : undefined)
}
: undefined), {
$set: {
status: factory.taskStatus.Running, // 実行中に変更
lastTriedAt: new Date(),
executor: params.executor
},
$inc: {
remainingNumberOfTries: -1, // 残りトライ可能回数減らす
numberOfTried: 1 // トライ回数増やす
}
}, { new: true, projection: executableTaskProjection })
.sort(sortOrder4executionOfTasks)
.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.lean() // lean(2024-09-26~)
.exec();
if (doc === null) {
// tslint:disable-next-line:no-null-keyword
return null;
}
return doc;
});
}
/**
* add(2025-03-16~)
*/
countPotentiallyRunning(params) {
return __awaiter(this, void 0, void 0, function* () {
const { runsAt, limit, name } = params;
const nameEq = name === null || name === void 0 ? void 0 : name.$eq;
const nameNin = name === null || name === void 0 ? void 0 : name.$nin;
if (!(runsAt.$lt instanceof Date)) {
throw new factory.errors.Argument('runsAt.$lt', 'must be Date');
}
const query = this.taskModel.countDocuments(Object.assign({ status: { $eq: factory.taskStatus.Ready }, runsAt: { $lt: params.runsAt.$lt } }, (typeof nameEq === 'string' || Array.isArray(nameNin))
? {
name: Object.assign(Object.assign({}, (typeof nameEq === 'string') ? { $eq: nameEq } : undefined), (Array.isArray(nameNin)) ? { $nin: nameNin } : undefined)
}
: undefined));
if (typeof limit === 'number' && limit >= 0) {
query.limit(limit);
}
const count = yield query.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.exec();
return { count };
});
}
emitRunningIfExists(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!(params.runsAt.$lt instanceof Date)) {
throw new factory.errors.Argument('runsAt.$lt', 'must be Date');
}
const projection = {
_id: 0,
id: { $toString: '$_id' },
name: 1
};
const nameEq = (_a = params.name) === null || _a === void 0 ? void 0 : _a.$eq;
const nameNin = (_b = params.name) === null || _b === void 0 ? void 0 : _b.$nin;
const doc = yield this.taskModel.findOneAndUpdate(Object.assign({ status: { $eq: factory.taskStatus.Ready }, runsAt: { $lt: params.runsAt.$lt } }, (typeof nameEq === 'string' || Array.isArray(nameNin))
? {
name: Object.assign(Object.assign({}, (typeof nameEq === 'string') ? { $eq: nameEq } : undefined), (Array.isArray(nameNin)) ? { $nin: nameNin } : undefined)
}
: undefined), {
$set: {
status: factory.taskStatus.Running, // 実行中に変更
lastTriedAt: new Date(),
executor: params.executor
},
$inc: {
remainingNumberOfTries: -1, // 残りトライ可能回数減らす
numberOfTried: 1 // トライ回数増やす
}
}, { new: true, projection })
.sort(params.sort)
.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.lean()
.exec();
if (doc === null) {
// tslint:disable-next-line:no-null-keyword
return null;
}
task_1.taskEventEmitter.emitTaskStatusChanged({
id: doc.id,
status: factory.taskStatus.Running
});
return doc;
});
}
findExecutableOne(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!(params.runsAt.$lt instanceof Date)) {
throw new factory.errors.Argument('runsAt.$lt', 'must be Date');
}
const nameEq = (_a = params.name) === null || _a === void 0 ? void 0 : _a.$eq;
const nameNin = (_b = params.name) === null || _b === void 0 ? void 0 : _b.$nin;
const query = this.taskModel.findOne(Object.assign({ status: { $eq: factory.taskStatus.Ready }, runsAt: { $lt: params.runsAt.$lt } }, (typeof nameEq === 'string' || Array.isArray(nameNin))
? {
name: Object.assign(Object.assign({}, (typeof nameEq === 'string') ? { $eq: nameEq } : undefined), (Array.isArray(nameNin)) ? { $nin: nameNin } : undefined)
}
: undefined), executableTaskProjection)
.sort({
runsAt: factory.sortType.Ascending
});
// .hint('executeOneByName');
// const explainResult = await query.explain();
// console.dir(explainResult, { depth: null });
// console.log(explainResult[0].executionStats.allPlansExecution.map((e: any) => e.executionStages.inputStage));
const doc = yield query.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.lean()
.exec();
if (doc === null) {
// tslint:disable-next-line:no-null-keyword
return null;
}
return doc;
});
}
// discontinue(2025-05-26~)
/**
* emit OnTaskStatusChanged on delayed tasks
*/
// public async emitDelayedTasksEvent(params: {
// now: Date;
// /**
// * 指定期間遅延しているタスクを実行する
// */
// delayInSeconds: number;
// limit: number;
// name: {
// /**
// * 指定タスクのみ実行する
// */
// $in?: factory.taskName[];
// /**
// * 指定タスク以外のみ実行する
// */
// $nin?: factory.taskName[];
// };
// }) {
// const runsAtLt = moment(params.now)
// .add(-params.delayInSeconds, 'seconds')
// .toDate();
// const projection: ProjectionType<factory.task.ITask<factory.taskName>> = {
// _id: 0,
// id: { $toString: '$_id' },
// name: 1,
// status: 1
// };
// const delayedTasks = await this.taskModel.find(
// {
// status: { $eq: factory.taskStatus.Ready },
// runsAt: { $lt: runsAtLt },
// ...(Array.isArray(params.name.$in)) ? { name: { $in: params.name.$in } } : undefined,
// ...(Array.isArray(params.name.$nin)) ? { name: { $nin: params.name.$nin } } : undefined
// },
// projection
// )
// .limit(params.limit)
// .sort(sortOrder4executionOfTasks) // sort(2025-03-03~)
// .setOptions({ maxTimeMS: MONGO_MAX_TIME_MS })
// .lean<IDelayedTask[]>() // lean(2024-09-26~)
// .exec();
// if (delayedTasks.length > 0) {
// delayedTasks.forEach((delayedTask) => {
// taskEventEmitter.emitTaskStatusChanged({
// id: delayedTask.id,
// name: delayedTask.name,
// status: factory.taskStatus.Ready
// });
// });
// }
// return delayedTasks;
// }
/**
* make tasks expired
*/
makeExpiredMany(params) {
return __awaiter(this, void 0, void 0, function* () {
const { expiresLt } = params;
if (!(expiresLt instanceof Date)) {
throw new factory.errors.Argument('expiresLt', 'must be Date');
}
return this.taskModel.updateMany({
status: { $eq: factory.taskStatus.Ready },
expires: { $exists: true, $lt: expiresLt }
}, {
$set: {
status: factory.taskStatus.Expired
}
})
.exec();
});
}
/**
* 実行中ステータスのままになっているタスクをリトライする
*/
retry(params) {
return __awaiter(this, void 0, void 0, function* () {
const lastTriedAtShoudBeLessThan = moment()
.add(-params.intervalInMinutes, 'minutes')
.toDate();
return this.taskModel.updateMany({
status: { $eq: factory.taskStatus.Running },
lastTriedAt: {
$type: 'date',
$lt: lastTriedAtShoudBeLessThan
},
remainingNumberOfTries: { $gt: 0 }
}, {
$set: {
status: factory.taskStatus.Ready // 実行前に変更
}
})
.exec();
});
}
/**
* 実行中止済タスクを強制的にリトライ
* 中止済タスクでなければ何もしない
*/
retryForciblyIfAborted(params) {
return __awaiter(this, void 0, void 0, function* () {
return this.taskModel.updateOne({
_id: { $eq: params.id },
status: { $eq: factory.taskStatus.Aborted }
}, {
$set: {
status: factory.taskStatus.Ready
},
$inc: {
remainingNumberOfTries: params.remainingNumberOfTries
}
})
.exec();
});
}
abortOne(params) {
return __awaiter(this, void 0, void 0, function* () {
const lastTriedAtShoudBeLessThan = moment()
.add(-params.intervalInMinutes, 'minutes')
.toDate();
const projection = Object.assign({ _id: 0, id: { $toString: '$_id' } }, Object.fromEntries(AVAILABLE_PROJECT_FIELDS.map((key) => ([key, 1]))));
const doc = yield this.taskModel.findOneAndUpdate({
status: { $eq: factory.taskStatus.Running },
lastTriedAt: {
$type: 'date',
$lt: lastTriedAtShoudBeLessThan
},
remainingNumberOfTries: { $eq: 0 }
}, {
$set: {
status: factory.taskStatus.Aborted,
dateAborted: new Date()
}
}, { new: true, projection })
.lean() // lean(2024-09-26~)
.exec();
if (doc === null) {
// tslint:disable-next-line:no-null-keyword
return null;
}
return doc;
});
}
abortMany(params) {
return __awaiter(this, void 0, void 0, function* () {
const lastTriedAtShoudBeLessThan = moment()
.add(-params.intervalInMinutes, 'minutes')
.toDate();
return this.taskModel.updateMany({
status: { $eq: factory.taskStatus.Running },
lastTriedAt: {
$type: 'date',
$lt: lastTriedAtShoudBeLessThan
},
remainingNumberOfTries: { $eq: 0 }
}, {
$set: {
status: factory.taskStatus.Aborted,
dateAborted: new Date()
}
})
.exec();
});
}
/**
* 実行結果を保管する
*/
pushExecutionResultById(params, executionResult,
// support customr function(2025-05-25~)
next) {
return __awaiter(this, void 0, void 0, function* () {
const { id, status } = params;
yield this.taskModel.updateOne({ _id: { $eq: id } }, {
$set: { status },
$push: { executionResults: executionResult }
})
.exec();
// emit event(2025-05-26~)
if (typeof next === 'function') {
task_1.taskEventEmitter.emitTaskStatusChanged({ id, status, executionResult }, next);
}
});
}
/**
* 特定タスク検索
*/
// public async findById<T extends factory.taskName>(params: {
// name: T;
// id: string;
// }): Promise<factory.task.ITask<T>> {
// const doc = await this.taskModel.findOne(
// {
// name: { $eq: params.name },
// _id: { $eq: params.id }
// },
// {
// __v: 0,
// createdAt: 0,
// updatedAt: 0
// }
// )
// .exec();
// if (doc === null) {
// throw new factory.errors.NotFound('Task');
// }
// return doc.toObject();
// }
count(params) {
return __awaiter(this, void 0, void 0, function* () {
const { limit } = params;
const conditions = TaskRepo.CREATE_MONGO_CONDITIONS(params);
const query = this.taskModel.countDocuments((conditions.length > 0) ? { $and: conditions } : {});
if (typeof limit === 'number' && limit >= 0) {
query.limit(limit);
}
const count = yield query.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.exec();
return { count };
});
}
/**
* 検索する
*/
projectFields(params,
// projection?: IProjection
inclusion) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const conditions = TaskRepo.CREATE_MONGO_CONDITIONS(params);
// const positiveProjectionExists: boolean = (projection !== undefined && projection !== null)
// ? Object.values(projection)
// .some((value) => value !== 0)
// : false;
let positiveProjectionFields = AVAILABLE_PROJECT_FIELDS;
if (Array.isArray(inclusion) && inclusion.length > 0) {
positiveProjectionFields = inclusion.filter((key) => AVAILABLE_PROJECT_FIELDS.includes(key));
}
else {
// no op
}
const projection = Object.assign({ _id: 0, id: { $toString: '$_id' } }, Object.fromEntries(positiveProjectionFields.map((key) => ([key, 1]))));
const query = this.taskModel.find((conditions.length > 0) ? { $and: conditions } : {}, projection);
if (typeof params.limit === 'number' && params.limit > 0) {
const page = (typeof params.page === 'number' && params.page > 0) ? params.page : 1;
query.limit(params.limit)
.skip(params.limit * (page - 1));
}
if (((_a = params.sort) === null || _a === void 0 ? void 0 : _a.runsAt) !== undefined) {
query.sort({ runsAt: params.sort.runsAt });
}
return query.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.lean() // lean(2024-09-26~)
.exec();
});
}
deleteByProject(params) {
return __awaiter(this, void 0, void 0, function* () {
yield this.taskModel.deleteMany({
'project.id': { $eq: params.project.id }
})
.exec();
});
}
deleteByName(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
return this.taskModel.deleteMany(Object.assign(Object.assign({ name: { $eq: params.name } }, (typeof ((_a = params.status) === null || _a === void 0 ? void 0 : _a.$eq) === 'string') ? { status: { $eq: params.status.$eq } } : undefined), (((_b = params.runsAt) === null || _b === void 0 ? void 0 : _b.$gte) instanceof Date)
? {
runsAt: { $gte: params.runsAt.$gte, $lte: params.runsAt.$lte }
}
: undefined))
.exec();
});
}
/**
* 不要なタスクを削除する
*/
deleteRunsAtPassedCertainPeriod(params) {
return __awaiter(this, void 0, void 0, function* () {
return this.taskModel.deleteMany({
runsAt: { $lt: params.runsAt.$lt },
status: {
$in: [
factory.taskStatus.Aborted,
factory.taskStatus.Executed,
factory.taskStatus.Expired
]
}
})
.exec();
});
}
countDelayedTasks(params) {
return __awaiter(this, void 0, void 0, function* () {
const { limit } = params;
const runsAtLt = moment()
.add(-params.delayInSeconds, 'seconds')
.toDate();
const query = this.taskModel.countDocuments(Object.assign({ status: { $eq: factory.taskStatus.Ready }, runsAt: { $lt: runsAtLt } }, (Array.isArray(params.name.$nin)) ? { name: { $nin: params.name.$nin } } : undefined));
if (typeof limit === 'number' && limit >= 0) {
query.limit(limit);
}
const count = yield query.setOptions({ maxTimeMS: settings_1.MONGO_MAX_TIME_MS })
.exec();
return { count };
});
}
getCursor(conditions, projection) {
return this.taskModel.find(conditions, projection)
.sort({ runsAt: factory.sortType.Ascending })
.cursor();
}
unsetUnnecessaryFields(params) {
return __awaiter(this, void 0, void 0, function* () {
return this.taskModel.updateMany(params.filter, { $unset: params.$unset }, { timestamps: false })
.exec();
});
}
aggregateTask(params) {
return __awaiter(this, void 0, void 0, function* () {
const statuses = yield Promise.all([
factory.taskStatus.Executed,
factory.taskStatus.Aborted
].map((taskStatus) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const matchConditions = Object.assign({ runsAt: {
$gte: params.runsFrom,
$lte: params.runsThrough
}, status: { $eq: taskStatus } }, (typeof ((_b = (_a = params.project) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.$ne) === 'string')
? { 'project.id': { $ne: params.project.id.$ne } }
: undefined);
return this.agggregateByStatus({ matchConditions, status: taskStatus });
})));
return { statuses };
});
}
// tslint:disable-next-line:max-func-body-length
agggregateByStatus(params) {
return __awaiter(this, void 0, void 0, function* () {
const matchConditions = params.matchConditions;
const taskStatus = params.status;
const aggregations = yield this.taskModel.aggregate([
{
$match: matchConditions
},
{
$project: {
latency: { $subtract: ['$lastTriedAt', '$runsAt'] },
status: '$status',
runsAt: '$runsAt',
lastTriedAt: '$lastTriedAt'
}
},
{
$group: {
_id: '$status',
taskCount: { $sum: 1 },
maxLatency: { $max: '$latency' },
minLatency: { $min: '$latency' },
avgLatency: { $avg: '$latency' }
}
},
{
$project: {
_id: 0,
taskCount: '$taskCount',
avgLatency: '$avgLatency',
maxLatency: '$maxLatency',
minLatency: '$minLatency'
}
}
])
.exec();
// tslint:disable-next-line:no-magic-numbers
const percents = [50, 95, 99];
if (aggregations.length === 0) {
return {
status: taskStatus,
aggregation: {
taskCount: 0,
avgLatency: 0,
maxLatency: 0,
minLatency: 0,
percentilesLatency: percents.map((percent) => {
return {
name: String(percent),
value: 0
};
})
}
};
}
const ranks4percentile = percents.map((percentile) => {
return {
percentile,
// tslint:disable-next-line:no-magic-numbers
rank: Math.floor(aggregations[0].taskCount * percentile / 100)
};
});
const aggregations2 = yield this.taskModel.aggregate([
{
$match: matchConditions
},
{
$project: {
latency: { $subtract: ['$lastTriedAt', '$runsAt'] },
status: '$status',
runsAt: '$runsAt',
lastTriedAt: '$lastTriedAt'
}
},
{ $sort: { latency: 1 } },
{
$group: {
_id: '$status',
latencies: { $push: '$latency' }
}
},
{
$project: {
_id: 0,
percentilesLatency: ranks4percentile.map((rank) => {
return {
name: String(rank.percentile),
value: { $arrayElemAt: ['$latencies', rank.rank] }
};
})
}
}
])
.exec();
return {
status: taskStatus,
aggregation: Object.assign(Object.assign({}, aggregations[0]), aggregations2[0])
};
});
}
}
exports.TaskRepo = TaskRepo;