UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

1,071 lines (1,070 loc) 46.2 kB
"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;