Steve Jobs is an all inclusive package for scheduling background jobs. It automates everything, so that you can get started with just Jobs.register
and Jobs.run
. At the same time, it allows you to configure and override just about any part of its functionality.
- Jobs.configure
- Jobs.register
- Jobs.run
- Jobs.find
- Jobs.execute
- Jobs.reschedule
- Jobs.replicate
- Jobs.start
- Jobs.stop
- Jobs.get
- Jobs.cancel
- Jobs.clear
- Jobs.remove
- Jobs.collection
Every Job document in stored into MongoDB because MongoDB has great persistence and querying features, and because you would not be required to set up any additional services to get running.
Here is an example of what a jobs document looks like:
{
_id: "riEauLYngjSGoETWh",
name: "sendEmail",
created: "2019-01-13T14:24:42.444Z",
serverId: "R7KPMHWz7DEsDWBCm",
state: "success",
due: "2019-01-13T14:24:51.444Z",
priority: 0,
arguments: ["sendReminder", "[email protected]", "The future is here!"],
history: [{
date: "2019-01-13T14:24:51.444Z,",
state: "success",
serverId: "R7KPMHWz7DEsDWBCm"
}, {
date: "2019-01-13T14:23:51.444Z",
type: "reschedule",
serverId: "5whJ8rWzDcTv8aZGy",
newDue: "2019-01-13T14:24:51.444Z"
}]
}
The history of each job is kept in the history
field, which can also hold the result of the job, if you pass it into the success
or failure
action.
Note: dates are not stored as strings, this was just a hack to get around markdown rendering issues
Jobs.configure
allows you to configure how the package should work. You can figure one option or all of them. All the options are pre-configured in ./package/server/imports/utilities/config.js
.
Jobs.configure({
// Key // Default Description
autoStart: Boolean, // true - specify if the package should start automatically on Meteor.startup
autoRetry: Boolean, // true - specify if the package should retry failed jobs whenever a new server takes control
autoPurge: Boolean, // true - specify if the package should automatically delete internal data (not job related)
interval: Number, // 3000 - specify how often the package should check for due jobs
startupDelay: Number, // 1000 - specify how long after server startup the package should start running
maxWait: Number, // 5min. - specify how long the server could be inactive before another server takes on the master role disableDevelopmentMode: Boolean, // development mode assumes that only one server is running, and that it is the active one
setServerId: Function, // Random.id () - determine how to set the serverId - for example, you can have the package use your hosts deployment id
getDate: Function, // new Date() - determine how to get the current date, if for whatever reason, new Date() is not suitable
log: Function, // console.log - determine how to log the package outputs
remoteCollection: String, // undefined - store jobs data in a remote collection
collectionName: String, // jobs_data - name of collection for where to store jobs
})
Jobs.register
(async) function that allows you to register logic for a job. Once registered, the package will start a queue to look for and execute jobs as appropriate, and you will be able to run jobs with Jobs.run
.
await Jobs.register({
sendEmail: async function (to, content) {
const send = await Magic.sendEmail(to, content);
if (!send) {
await this.reschedule({
in: {
minutes: 5
}
});
}
},
sendReminder: async function (userId, content) {
const doc = Reminders.insert({
to: userId,
content: content
});
if (doc) {
await this.remove();
} else {
await this.reschedule({
in: {
minutes: 5
}
});
}
}
});
Each job is binded with a set of functions to give you maximum control over how the job runs:
this.document
- access job documentthis.set(key, value)
- set a persistent key/value pairthis.get(key)
- get a persistent value from keythis.failure(result)
- tell the queue the job failed, and attach an optional resultthis.reschedule(config)
- tell the queue to schedule the job for a future datethis.remove()
- remove the job from the queue
Each job must be resolved with success, failure, reschedule, and/or remove, otherwise it will loop infinitely. Not to worry, the queue is smart enough to pause itself if that happens.
Jobs.run
(async) allows you to schedule a job to run. You call it just like you would call a method, by specifying the job namea and its arguments. At the end, you can pass in a special configuration object. Otherwise, it will be scheduled to run as soon as possible.
await Jobs.run("sendReminder", "[email protected]", "The future is here!", {
in: {
days: 3,
},
on: {
hour: 9,
minute: 42
},
priority: 9999999999,
singular: true
});
The configuration object supports the following inputs:
in
- Object- The
in
parameter will schedule the job at a later time, using the current time and your inputs to calculate the due time.
- The
on
- Object- The
on
parameter override the current time with your inputs.
- The
in
andon
- Object- The supported fields for in and on can be used in singular and/or plural versions:
- millisecond, second, minute, hour, day, month, and year
- milliseconds, seconds, minutes, hours, days, months, and years
- The date object will be updated in the order that is specified. This means that if it is year 2017, and you set
in
one year, buton
2019, the year 2019 will be the final result. However, if you seton
2019 andin
one year, then the year 2020 will be the final result.
- The supported fields for in and on can be used in singular and/or plural versions:
priority
- Number- The default priority for each job is 0
- If you set it to a positive integer, it will run ahead of other jobs.
- If you set it to a negative integer, it will only run after all the zero or positive jobs have completed.
date
- Function- Provide your own date. This stacks with the
in
andon
operator, and will be applied before they perform their operations.
- Provide your own date. This stacks with the
unique
- Boolean- If a job is marked as unique, it will only be scheduled if no other job exists with the same arguments
singular
- Boolean- If a job is marked as singular, it will only be scheduled if no other job is pending (or failed, which in effect, is the same as pending) with the same arguments
callback
- Function- Run a callback function after scheduling the job
Jobs.find
(async) allows you to find a single pending job by its arguments, and run logic against it. You can pass in a callback in the end to check for the document, and you can run the same operations with-in the function scope as you would with Jobs.register
, as well as this.run
as a shortcut to Jobs.run
.
await Jobs.find("sendReminder", "[email protected]", "The future is here!", async function (jobDoc) {
if (jobDoc) {
await this.reschedule({
in: {
minutes: 5
}
});
return jobDoc;
} else {
await this.run({
in: {
minutes: 5
}
});
}
});
Jobs.execute
(async) allows you to run a job ahead of its due date. It can only work on jobs that have not been resolved.
await Jobs.execute(docId)
Jobs.reschedule
(async) allows you to reschedule a job. It can only work on jobs that have not been resolved.
await Jobs.reschedule(jobId, {
in: {
minutes: 5
},
priority: 99999999
})
The configuration is passed in as the second argument, and it supports the same inputs as Jobs.run
.
Jobs.replicate
(async) allows you to replicate a job.
await Jobs.replicate(jobId, {
in: {
minutes: 5
}
})
Jobs.start
(async) allows you start all the queues. This runs automatically unless autoStart
is set to false
. If you call the function with no arguments, it will start all the queues. If you pass in a String, it will start a queue with that name. If you pass in an Array, it will loop over the items in it, and treat them like a string.
// Start all the queues
await Jobs.start()
// Start just one queue
await Jobs.start("sendReminder")
// Start multiple queues
await Jobs.start(["sendReminder", "sendEmail"])
This function currently only works on the server where it is called.
Jobs.stop
(async) allows you stop all the queues. If you call the function with no arguments, it will stop all the queues. If you pass in a String, it will stop a queue with that name. If you pass in an Array, it will loop over the items in it, and treat them like a string.
// Stop all the queues
await Jobs.stop()
// Stop just one queue
await Jobs.stop("sendReminder")
// Stop multiple queues
await Jobs.stop(["sendReminder", "sendEmail"])
This function currently only works on the server where it is called.
Jobs.get
(async) allows you to get a job document by its document id.
await Jobs.get(docId)
A job document looks like this:
{
_id: 'BqjPbF9NGxY4YdnGn',
name: 'sendEmail',
created: '2018-05-18T09:48:48.355Z',
serverId: '7NrBe4QyDsYjxK9xg',
state: 'success',
due: '2018-05-18T09:48:48.355Z',
priority: 0,
arguments: ['[email protected]', 'Hello again'],
history: [{
date: '2018-05-18T09:48:57.492Z',
state: 'success',
serverId: '7NrBe4QyDsYjxK9xg'
}]
}
The configuration is passed in as the second argument, and it supports the same inputs as Jobs.run
.
Jobs.cancel
(async) allows you to cancel a job if it has not run already.
await Jobs.cancel(jobId)
Jobs.clear
(async) allows you to clear all or some of the jobs in your database. It supports state
for selecting a job state, which can be pending
, success
, or failure
, or *"
to select all of them.
You can add the name
arguments to specify a specific queue. You can also call an optional callback.
var state = "pending";
var name = "sendEmail";
var cb = function (r) { console.log(r) }
await Jobs.clear(state, name, cb)
Jobs.remove
(async) allows you to remove a job from the collection.
await Jobs.remove(docId)
Jobs.collection
allows you to access the MongoDB collection where the jobs are stored. Ideally, you should not require interaction with the database directly, but hey, we're developers. If you find a case where this is necessary, let me know.