UNPKG

@vendure/core

Version:

A modern, headless ecommerce framework

217 lines 9.68 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultSchedulerStrategy = void 0; const ms_1 = __importDefault(require("ms")); const utils_1 = require("../../common/utils"); const config_service_1 = require("../../config/config.service"); const vendure_logger_1 = require("../../config/logger/vendure-logger"); const connection_1 = require("../../connection"); const process_context_1 = require("../../process-context"); const constants_1 = require("./constants"); const scheduled_task_record_entity_1 = require("./scheduled-task-record.entity"); /** * @description * The default {@link SchedulerStrategy} implementation that uses the database to * execute scheduled tasks. This strategy is configured when you use the * {@link DefaultSchedulerPlugin}. * * @since 3.3.0 * @docsCategory scheduled-tasks */ class DefaultSchedulerStrategy { constructor() { this.tasks = new Map(); this.runningTasks = []; } init(injector) { this.connection = injector.get(connection_1.TransactionalConnection); this.pluginOptions = injector.get(constants_1.DEFAULT_SCHEDULER_PLUGIN_OPTIONS); this.injector = injector; const runTriggerCheck = injector.get(config_service_1.ConfigService).schedulerOptions.runTasksInWorkerOnly === false || injector.get(process_context_1.ProcessContext).isWorker; if (runTriggerCheck) { this.intervalRef = setInterval(() => this.checkForManuallyTriggeredTasks(), this.pluginOptions.manualTriggerCheckInterval); } } async destroy() { if (this.intervalRef) { clearInterval(this.intervalRef); } for (const task of this.runningTasks) { await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .update({ taskId: task.id }, { lockedAt: null }); vendure_logger_1.Logger.info(`Released lock for task "${task.id}"`); } } registerTask(task) { this.tasks.set(task.id, { task, isRegistered: false, }); } executeTask(task) { return async (job) => { var _a; await this.ensureTaskIsRegistered(task); const taskEntity = await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .update() .set({ lockedAt: new Date() }) .where('taskId = :taskId', { taskId: task.id }) .andWhere('lockedAt IS NULL') .andWhere('enabled = TRUE') .execute(); if (!taskEntity.affected) { return; } vendure_logger_1.Logger.verbose(`Executing scheduled task "${task.id}"`); try { this.runningTasks.push(task); const timeout = (_a = task.options.timeout) !== null && _a !== void 0 ? _a : this.pluginOptions.defaultTimeout; const timeoutMs = typeof timeout === 'number' ? timeout : (0, ms_1.default)(timeout); let timeoutTimer; const timeoutPromise = new Promise((_, reject) => { timeoutTimer = setTimeout(() => { vendure_logger_1.Logger.warn(`Scheduled task ${task.id} timed out after ${timeoutMs}ms`); reject(new Error('Task timed out')); }, timeoutMs); }); const result = await Promise.race([task.execute(this.injector), timeoutPromise]); if (timeoutTimer) { clearTimeout(timeoutTimer); } await this.connection.rawConnection.getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord).update({ taskId: task.id, }, { lastExecutedAt: new Date(), lockedAt: null, lastResult: result !== null && result !== void 0 ? result : '', }); vendure_logger_1.Logger.verbose(`Scheduled task "${task.id}" completed successfully`); this.runningTasks = this.runningTasks.filter(t => t !== task); } catch (error) { let errorMessage = 'Unknown error'; if (error instanceof Error) { errorMessage = error.message; } vendure_logger_1.Logger.error(`Scheduled task "${task.id}" failed with error: ${errorMessage}`); await this.connection.rawConnection.getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord).update({ taskId: task.id, }, { lockedAt: null, lastResult: { error: errorMessage }, }); this.runningTasks = this.runningTasks.filter(t => t !== task); } }; } async getTasks() { await this.ensureAllTasksAreRegistered(); return this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .getMany() .then(tasks => { return tasks.map(task => this.entityToReport(task)); }); } async getTask(id) { await this.ensureTaskIsRegistered(id); return this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .where('task.taskId = :id', { id }) .getOne() .then(task => (task ? this.entityToReport(task) : undefined)); } async updateTask(input) { await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .update() .set({ enabled: input.enabled }) .where('taskId = :id', { id: input.id }) .execute(); return (0, utils_1.assertFound)(this.getTask(input.id)); } async triggerTask(task) { vendure_logger_1.Logger.info(`Triggering task: ${task.id}`); await this.ensureTaskIsRegistered(task); await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .update() .set({ manuallyTriggeredAt: new Date() }) .where('taskId = :id', { id: task.id }) .execute(); } async checkForManuallyTriggeredTasks() { // Since this is run on an interval, there is an edge case where, during shutdown, // the connection may not be initialized anymore. if (!this.connection.rawConnection.isInitialized) { return; } let taskEntities = []; try { taskEntities = await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder('task') .where('task.manuallyTriggeredAt IS NOT NULL') .getMany(); } catch (e) { // This branch can be reached if the connection is closed and then this method // is called on the interval. Usually encountered in tests. const errorMessage = e instanceof Error ? e.message : 'Unknown error'; vendure_logger_1.Logger.error(`Error checking for manually triggered tasks: ${errorMessage}`); } vendure_logger_1.Logger.debug(`Checking for manually triggered tasks: ${taskEntities.length}`); for (const taskEntity of taskEntities) { await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .update({ taskId: taskEntity.taskId }, { manuallyTriggeredAt: null }); const task = this.tasks.get(taskEntity.taskId); if (task) { vendure_logger_1.Logger.info(`Executing manually triggered task: ${task.task.id}`); void this.executeTask(task.task)(); } } } entityToReport(task) { return { id: task.taskId, lastExecutedAt: task.lastExecutedAt, isRunning: task.lockedAt !== null, lastResult: task.lastResult, enabled: task.enabled, }; } async ensureAllTasksAreRegistered() { for (const task of this.tasks.values()) { await this.ensureTaskIsRegistered(task.task); } } async ensureTaskIsRegistered(taskOrId) { const taskId = typeof taskOrId === 'string' ? taskOrId : taskOrId.id; const task = this.tasks.get(taskId); if (task && !task.isRegistered) { await this.connection.rawConnection .getRepository(scheduled_task_record_entity_1.ScheduledTaskRecord) .createQueryBuilder() .insert() .into(scheduled_task_record_entity_1.ScheduledTaskRecord) .values({ taskId }) .orIgnore() .execute(); this.tasks.set(taskId, { task: task.task, isRegistered: true }); } } } exports.DefaultSchedulerStrategy = DefaultSchedulerStrategy; //# sourceMappingURL=default-scheduler-strategy.js.map