Skip to content

Latest commit

 

History

History
executable file
·
318 lines (249 loc) · 10.9 KB

DOCUMENTATION.md

File metadata and controls

executable file
·
318 lines (249 loc) · 10.9 KB

Steve Jobs Documentation [DRAFT]

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.

Overview of a Job Document

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

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

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 document
  • this.set(key, value) - set a persistent key/value pair
  • this.get(key) - get a persistent value from key
  • this.failure(result) - tell the queue the job failed, and attach an optional result
  • this.reschedule(config) - tell the queue to schedule the job for a future date
  • this.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

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.
  • on - Object
    • The on parameter override the current time with your inputs.
  • in and on - 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, but on 2019, the year 2019 will be the final result. However, if you set on 2019 and in one year, then the year 2020 will be the final result.
  • 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 and on operator, and will be applied before they perform their operations.
  • 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

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

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

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

Jobs.replicate (async) allows you to replicate a job.

await Jobs.replicate(jobId, {
	in: {
		minutes: 5
	}
})

Jobs.start

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

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

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

Jobs.cancel (async) allows you to cancel a job if it has not run already.

await Jobs.cancel(jobId)

Jobs.clear

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

Jobs.remove (async) allows you to remove a job from the collection.

await Jobs.remove(docId)

Jobs.collection

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.