actionhero
Version:
The reusable, scalable, and quick node.js API server for stateless and stateful applications
189 lines (162 loc) • 5.41 kB
text/typescript
import * as path from "path";
import { Plugin } from "node-resque";
import * as TaskModule from "./../modules/task";
import { api, config, log, utils, task, Initializer } from "../index";
import { Task } from "../classes/task";
import { safeGlobSync } from "../modules/utils/safeGlob";
const taskModule = task;
export interface TaskApi {
tasks: { [key: string]: Task };
jobs: { [key: string]: any };
middleware: { [key: string]: TaskModule.task.TaskMiddleware };
globalMiddleware: Array<string>;
loadFile: TasksInitializer["loadFile"];
jobWrapper: TasksInitializer["jobWrapper"];
loadTasks: TasksInitializer["loadTasks"];
}
/**
* Tools for enqueuing and inspecting the task system (delayed jobs).
*/
export class TasksInitializer extends Initializer {
constructor() {
super();
this.name = "tasks";
this.loadPriority = 699;
this.startPriority = 975;
}
loadFile = async (fullFilePath: string, reload = false) => {
let task;
let collection = await import(fullFilePath);
for (const i in collection) {
const TaskClass = collection[i];
task = new TaskClass();
task.validate();
if (api.tasks.tasks[task.name] && !reload) {
log(
`an existing task with the same name \`${task.name}\` will be overridden by the file ${fullFilePath}`,
"crit",
);
}
api.tasks.tasks[task.name] = task;
api.tasks.jobs[task.name] = api.tasks.jobWrapper(task.name);
log(
`task ${reload ? "(re)" : ""} loaded: ${task.name}, ${fullFilePath}`,
"debug",
);
}
};
jobWrapper = (taskName: string) => {
const task = api.tasks.tasks[taskName];
const middleware = task.middleware || [];
const plugins = task.plugins || [];
const pluginOptions = task.pluginOptions || {};
if (task.frequency > 0) {
if (plugins.indexOf("JobLock") < 0) {
plugins.push("JobLock");
pluginOptions.JobLock = { reEnqueue: false };
}
if (plugins.indexOf("QueueLock") < 0) {
plugins.push("QueueLock");
}
if (plugins.indexOf("DelayQueueLock") < 0) {
plugins.push("DelayQueueLock");
}
}
// load middleware into plugins
const processMiddleware = (m: string) => {
if (api.tasks.middleware[m]) {
class NodeResquePlugin extends Plugin {
constructor(...args: ConstructorParameters<typeof Plugin>) {
super(...args);
}
async beforeEnqueue() {
return api.tasks.middleware[m].preEnqueue
? api.tasks.middleware[m].preEnqueue.call(this)
: true;
}
async afterEnqueue() {
return api.tasks.middleware[m].postEnqueue
? api.tasks.middleware[m].postEnqueue.call(this)
: true;
}
async beforePerform() {
return api.tasks.middleware[m].preProcessor
? api.tasks.middleware[m].preProcessor.call(this)
: true;
}
async afterPerform() {
return api.tasks.middleware[m].postProcessor
? api.tasks.middleware[m].postProcessor.call(this)
: true;
}
}
//@ts-ignore
plugins.push(NodeResquePlugin);
}
};
api.tasks.globalMiddleware.forEach(processMiddleware);
middleware.forEach(processMiddleware);
return {
plugins,
pluginOptions,
perform: async function () {
const combinedArgs = [].concat(Array.prototype.slice.call(arguments));
combinedArgs.push(this);
let response = null;
try {
response = await task.run.apply(task, combinedArgs);
await taskModule.enqueueRecurrentTask(taskName);
} catch (error) {
if (task.frequency > 0 && task.reEnqueuePeriodicTaskIfException) {
await taskModule.enqueueRecurrentTask(taskName);
}
throw error;
}
return response;
},
};
};
loadTasks = async (reload: boolean) => {
for (const p of config.general.paths.task) {
await Promise.all(
utils
.ensureNoTsHeaderOrSpecFiles(
safeGlobSync(path.join(p, "**", "**/*(*.js|*.ts)")),
)
.map((f) => api.tasks.loadFile(f, reload)),
);
}
for (const plugin of Object.values(config.plugins)) {
if (plugin.tasks !== false) {
const pluginPath = path.normalize(plugin.path);
// old style at the root of the project
let files = safeGlobSync(path.join(pluginPath, "tasks", "**", "*.js"));
files = files.concat(
safeGlobSync(path.join(pluginPath, "dist", "tasks", "**", "*.js")),
);
utils.ensureNoTsHeaderOrSpecFiles(files).forEach((f) => {
api.tasks.loadFile(f, reload);
});
}
}
};
async initialize() {
api.tasks = {
tasks: {},
jobs: {},
middleware: {},
globalMiddleware: [],
loadFile: this.loadFile,
jobWrapper: this.jobWrapper,
loadTasks: this.loadTasks,
};
await api.tasks.loadTasks(false);
// we want to start the queue now, so that it's available for other initializers and CLI commands
await api.resque.startQueue();
}
async start() {
if (config.tasks.scheduler === true) {
await taskModule.enqueueAllRecurrentTasks();
}
}
}