UNPKG

@hokify/agenda

Version:

Light weight job scheduler for Node.js

366 lines 13.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Agenda = void 0; const events_1 = require("events"); const debug = require("debug"); const Job_1 = require("./Job"); const JobDbRepository_1 = require("./JobDbRepository"); const priority_1 = require("./utils/priority"); const JobProcessor_1 = require("./JobProcessor"); const processEvery_1 = require("./utils/processEvery"); const stack_1 = require("./utils/stack"); const log = debug('agenda'); const DefaultOptions = { processEvery: 5000, defaultConcurrency: 5, maxConcurrency: 20, defaultLockLimit: 0, lockLimit: 0, defaultLockLifetime: 10 * 60 * 1000, sort: { nextRunAt: 1, priority: -1 }, forkHelper: { path: 'dist/childWorker.js' } }; /** * @class */ class Agenda extends events_1.EventEmitter { on(event, listener) { if (this.forkedWorker && event !== 'ready' && event !== 'error') { const warning = new Error(`calling on(${event}) during a forkedWorker has no effect!`); console.warn(warning.message, warning.stack); return this; } return super.on(event, listener); } isActiveJobProcessor() { return !!this.jobProcessor; } async getForkedJob(jobId) { const jobData = await this.db.getJobById(jobId); if (!jobData) { throw new Error('db entry not found'); } const job = new Job_1.Job(this, jobData); return job; } async getRunningStats(fullDetails = false) { if (!this.jobProcessor) { throw new Error('agenda not running!'); } return this.jobProcessor.getStatus(fullDetails); } /** * @param {Object} config - Agenda Config * @param {Function} cb - Callback after Agenda has started and connected to mongo */ constructor(config = DefaultOptions, cb) { super(); this.definitions = {}; this.attrs = { name: config.name || '', processEvery: (0, processEvery_1.calculateProcessEvery)(config.processEvery) || DefaultOptions.processEvery, defaultConcurrency: config.defaultConcurrency || DefaultOptions.defaultConcurrency, maxConcurrency: config.maxConcurrency || DefaultOptions.maxConcurrency, defaultLockLimit: config.defaultLockLimit || DefaultOptions.defaultLockLimit, lockLimit: config.lockLimit || DefaultOptions.lockLimit, defaultLockLifetime: config.defaultLockLifetime || DefaultOptions.defaultLockLifetime, sort: config.sort || DefaultOptions.sort }; this.forkedWorker = config.forkedWorker; this.forkHelper = config.forkHelper; this.ready = new Promise(resolve => { this.once('ready', resolve); }); if (this.hasDatabaseConfig(config)) { this.db = new JobDbRepository_1.JobDbRepository(this, config); this.db.connect(); } if (cb) { this.ready.then(() => cb()); } } /** * Connect to the spec'd MongoDB server and database. */ async database(address, collection, options) { this.db = new JobDbRepository_1.JobDbRepository(this, { db: { address, collection, options } }); await this.db.connect(); return this; } /** * Use existing mongo connectino to pass into agenda * @param mongo * @param collection */ async mongo(mongo, collection) { this.db = new JobDbRepository_1.JobDbRepository(this, { mongo, db: { collection } }); await this.db.connect(); return this; } /** * Set the sort query for finding next job * Default is { nextRunAt: 1, priority: -1 } * @param query */ sort(query) { log('Agenda.sort([Object])'); this.attrs.sort = query; return this; } hasDatabaseConfig(config) { var _a; return !!(((_a = config === null || config === void 0 ? void 0 : config.db) === null || _a === void 0 ? void 0 : _a.address) || (config === null || config === void 0 ? void 0 : config.mongo)); } /** * Cancels any jobs matching the passed MongoDB query, and removes them from the database. * @param query */ async cancel(query) { log('attempting to cancel all Agenda jobs', query); try { const amountOfRemovedJobs = await this.db.removeJobs(query); log('%s jobs cancelled', amountOfRemovedJobs); return amountOfRemovedJobs; } catch (error) { log('error trying to delete jobs from MongoDB'); throw error; } } /** * Set name of queue * @param name */ name(name) { log('Agenda.name(%s)', name); this.attrs.name = name; return this; } /** * Set the time how often the job processor checks for new jobs to process * @param time */ processEvery(time) { if (this.jobProcessor) { throw new Error('job processor is already running, you need to set processEvery before calling start'); } log('Agenda.processEvery(%d)', time); this.attrs.processEvery = (0, processEvery_1.calculateProcessEvery)(time); return this; } /** * Set the concurrency for jobs (globally), type does not matter * @param num */ maxConcurrency(num) { log('Agenda.maxConcurrency(%d)', num); this.attrs.maxConcurrency = num; return this; } /** * Set the default concurrency for each job * @param num number of max concurrency */ defaultConcurrency(num) { log('Agenda.defaultConcurrency(%d)', num); this.attrs.defaultConcurrency = num; return this; } /** * Set the default amount jobs that are allowed to be locked at one time (GLOBAL) * @param num */ lockLimit(num) { log('Agenda.lockLimit(%d)', num); this.attrs.lockLimit = num; return this; } /** * Set default lock limit per job type * @param num */ defaultLockLimit(num) { log('Agenda.defaultLockLimit(%d)', num); this.attrs.defaultLockLimit = num; return this; } /** * Set the default lock time (in ms) * Default is 10 * 60 * 1000 ms (10 minutes) * @param ms */ defaultLockLifetime(ms) { log('Agenda.defaultLockLifetime(%d)', ms); this.attrs.defaultLockLifetime = ms; return this; } /** * Finds all jobs matching 'query' * @param query * @param sort * @param limit * @param skip */ async jobs(query = {}, sort = {}, limit = 0, skip = 0) { const result = await this.db.getJobs(query, sort, limit, skip); return result.map(job => new Job_1.Job(this, job)); } /** * Removes all jobs from queue * @note: Only use after defining your jobs */ async purge() { const definedNames = Object.keys(this.definitions); log('Agenda.purge(%o)', definedNames); return this.cancel({ name: { $not: { $in: definedNames } } }); } define(name, processor, options) { if (this.definitions[name]) { log('overwriting already defined agenda job', name); } const filePath = (0, stack_1.getCallerFilePath)(); this.definitions[name] = { fn: processor, filePath, concurrency: (options === null || options === void 0 ? void 0 : options.concurrency) || this.attrs.defaultConcurrency, lockLimit: (options === null || options === void 0 ? void 0 : options.lockLimit) || this.attrs.defaultLockLimit, priority: (0, priority_1.parsePriority)(options === null || options === void 0 ? void 0 : options.priority), lockLifetime: (options === null || options === void 0 ? void 0 : options.lockLifetime) || this.attrs.defaultLockLifetime }; log('job [%s] defined with following options: \n%O', name, this.definitions[name]); } /** * Internal helper method that uses createJob to create jobs for an array of names * @param {Number} interval run every X interval * @param {Array<String>} names Strings of jobs to schedule * @param {Object} data data to run for job * @param {Object} options options to run job for * @returns {Array<Job>} array of jobs created */ async createJobs(names, createJob) { try { const jobs = await Promise.all(names.map(name => createJob(name))); log('createJobs() -> all jobs created successfully'); return jobs; } catch (error) { log('createJobs() -> error creating one or more of the jobs', error); throw error; } } create(name, data) { log('Agenda.create(%s, [Object])', name); const priority = this.definitions[name] ? this.definitions[name].priority : 0; const job = new Job_1.Job(this, { name, data, type: 'normal', priority }); return job; } async every(interval, names, data, options) { /** * Internal method to setup job that gets run every interval * @param {Number} interval run every X interval * @param {String} name String job to schedule * @param {Object} data data to run for job * @param {Object} options options to run job for * @returns {Job} instance of job */ log('Agenda.every(%s, %O, %O)', interval, names, options); const createJob = async (name) => { const job = this.create(name, data); job.attrs.type = 'single'; job.repeatEvery(interval, options); if (options === null || options === void 0 ? void 0 : options.forkMode) { job.forkMode(options.forkMode); } await job.save(); return job; }; if (typeof names === 'string') { const job = await createJob(names); return job; } log('Agenda.every(%s, %s, %O)', interval, names, options); const jobs = await this.createJobs(names, createJob); return jobs; } async schedule(when, names, data) { const createJob = async (name) => { const job = this.create(name, data); await job.schedule(when).save(); return job; }; if (typeof names === 'string') { log('Agenda.schedule(%s, %O, [%O])', when, names); return createJob(names); } log('Agenda.schedule(%s, %O, [%O])', when, names); return this.createJobs(names, createJob); } async now(name, data) { log('Agenda.now(%s, [Object])', name); try { const job = this.create(name, data); job.schedule(new Date()); await job.save(); return job; } catch (error) { log('error trying to create a job for this exact moment'); throw error; } } /** * Starts processing jobs using processJobs() methods, storing an interval ID * This method will only resolve if a db has been set up beforehand. */ async start() { log('Agenda.start called, waiting for agenda to be initialized (db connection)', this.attrs.processEvery); await this.ready; if (this.jobProcessor) { log('Agenda.start was already called, ignoring'); return; } this.jobProcessor = new JobProcessor_1.JobProcessor(this, this.attrs.maxConcurrency, this.attrs.lockLimit, this.attrs.processEvery); this.on('processJob', this.jobProcessor.process.bind(this.jobProcessor)); } /** * Clear the interval that processes the jobs and unlocks all currently locked jobs */ async stop() { if (!this.jobProcessor) { log('Agenda.stop called, but agenda has never started!'); return; } log('Agenda.stop called, clearing interval for processJobs()'); const lockedJobs = this.jobProcessor.stop(); log('Agenda._unlockJobs()'); lockedJobs === null || lockedJobs === void 0 ? void 0 : lockedJobs.forEach(job => job.cancel(new Error('agenda stopped'))); const jobIds = (lockedJobs === null || lockedJobs === void 0 ? void 0 : lockedJobs.map(job => job.attrs._id)) || []; if (jobIds.length > 0) { log('about to unlock jobs with ids: %O', jobIds); await this.db.unlockJobs(jobIds); } this.off('processJob', this.jobProcessor.process.bind(this.jobProcessor)); this.jobProcessor = undefined; } } exports.Agenda = Agenda; __exportStar(require("./types/AgendaConfig"), exports); __exportStar(require("./types/JobDefinition"), exports); __exportStar(require("./types/JobParameters"), exports); __exportStar(require("./types/DbOptions"), exports); __exportStar(require("./Job"), exports); //# sourceMappingURL=index.js.map