Jobs
Jobs allow you to execute tasks in the background, either manually or on a schedule as with cron (opens in a new tab). Jobs are Keel's answer to the collection of random scripts that every team ends up with and that one cron job which everyone forgets about but actually keeps the business running.
Jobs are written as normal TypeScript functions and have full access to your database. You can view job runs from the Keel console along with full traces and logs for easy debugging. You can limit who can run each job with role-based permission rules. Jobs can also accept inputs, making it possible to build simple but powerful internal tools.
Getting Started
Jobs are defined in your schema, with the implementation written in a TypeScript function.
job EmailNewCustomerReport {
@schedule("every weekday at 9am")
}
This example defines a scheduled job called EmailNewCustomerReport
that will run every weekday (Monday-Friday) at 9am.
To scaffold out the code for this job you can run keel generate
, which will create a new file in the ./jobs
directory called emailNewCustomerReport.ts. This file will look something like this:
import { EmailNewCustomerReport } from "@teamkeel/sdk";
export default EmailNewCustomerReport(async (ctx) => {
// your code here
});
Your job code can do whatever you want - call 3rd party APIs or use packages from npm. You can also interact with the database in the same way you would in functions, using the APIs available in the @teamkeel/sdk
package.
Limitations
The following limitations currently apply to jobs:
- Jobs have 512MB of disk space available to them
- A job can only run for 15 minutes, after this period it will be killed and marked as failed
Scheduled jobs
A job can be scheduled by using the @schedule
attribute, which accepts a string that defines the schedule. The schedule string can either be a simple human-readable expression or for more complex cases a unix-style cron expression.
Defining a schedule
The human-readable form starts with the word every
and then either describes a time interval (e.g. every 10 minutes
), or a day and time (e.g. every monday at 9am
). The interval form also allows for a time-range, for example every 2 hours from 8am to 6pm
. Although the syntax of these expressions is very simple they support a lot of common scheduling patterns and have the benefit of being much easier to read than cron.
job EmailNewCustomerReport {
@schedule("every monday at 9am")
}
These are the formats the human-readable schedule format accepts:
every N minutes
every N hours
every N minutes from T to T
every N hours from T to T
every D at T
Where N
is an integer, T
is a 12-hour time, and D
is a day of the week. D
can also be day
for everyday or weekday
for Monday-Friday.
In the format every D at T
both D
and T
can be a list of values, for example every monday and tuesday at 9am, 12pm, and 3pm
. The values must be comma-separated but the word and
is also allowed to improve readability.
The cron form uses unix-style crontab, with five fields corresponding to minute, hour, day-of-month, month, and day-of-week. An example being 0 9 * * 1
, which would trigger every Monday at 9am. Cron allows for very complex scheduling, for example */5 6-8,15-17 * 1 1,3,5
, which would trigger every five minutes between 6am and 8am on Mondays, Wednesdays, and Fridays in January.
job EmailNewCustomerReport {
@schedule("0 9 * * 1")
}
The table below shows examples of the human-readable form with the equivalent cron syntax.
Human-readable | Cron |
---|---|
"every 10 minutes" | "*/10 * * * *" |
"every 30 minutes from 10am to 2pm" | "*/30 10-14 * * *" |
"every 2 hours" | "0 */2 * * *" |
"every 2 hours from 9am to 3pm" | "0 9,11,13,15 * * *" |
"every monday at 9am" | "0 9 * * 1" |
"every tuesday and thursday at 8am, 12pm, and 5pm" | "0 8,12,17 * * 2,4" |
If you're not familiar with cron syntax you can use a tool like crontab.guru (opens in a new tab) to help you build your schedule.
Validation
Keel will check your job's schedule at build time to make sure it is valid. Examples of validation errors are:
- Having more than one
@schedule
attribute per job definition - A schedule that doesn't divide evenly into the time-unit e.g.
every 5 hours
would be an error because 24 does not divide evently by 5 - Invalid cron syntax e.g.
0 9am * * *
would result in the errorinvalid value '9am' for the hours field
Manual jobs
To be able to manually run a job from the console you must define a permission rule that specifies which members of your team are allowed to run the job. The following schema defines a DoImportantThing
job which anyone with access to your Keel project can run.
job DoImportantThing {
@permission(expression: true)
}
It's valid to add a @permission
attribute to a job that also uses @schedule
, which means the job will both run on the schedule and can be manually run from the console. Manual runs will not affect the scheduled runs in any way.
Permissions
The previous example shows how to create a job that anyone with access to your project can run, but what if you only want to allow certain people in your team to be able to run a job? This is where role-based permissions are really useful.
The BigDataImport
job defined in the following schema can only be run by someone with the Developer
role, which is assigned to two members of the team - Sally and Barry.
job BigDataImport {
@permission(roles: [Developer])
}
role Developer {
emails {
"sally@corp.com"
"barry@corp.com"
}
}
The expression true
when used in a job permission rule means that anyone
with access to your project can trigger the job. Jobs are never publicly
accessible.
Inputs
Manual jobs can accept inputs which are provided when the job is run, allowing you to easily create internal tools for your whole team from simple functions. Any inputs defined in a job will be displayed as a form that is shown when trying to run the job in the console.
The following schema defines a RefundOrder
job that accepts the order ID as an input.
job RefundOrder {
inputs {
orderId ID
}
@permission(expression: true)
}
Whilst it is fine to have a scheduled job that also defines permissions for manual running, it is not valid to specify inputs for a job that is scheduled.
Jobs in the Console
From the Jobs page in the console you can see all the jobs defined in your project, when they last ran, and if applicable their schedule.
The Run history tab shows all previous job runs. For each job you can see when it ran, if it ran successfully, and if manually executed who ran it. Clicking on an individual run row will show you the full trace and logs for that run, which can be useful for debugging failed jobs.
Testing
To help you develop your jobs, Keel makes them available within tests. When calling a job from your test make sure to await
it.
import { jobs, actions, resetDatabase } from "@teamkeel/testing";
import { test, expect, beforeEach } from "vitest";
beforeEach(resetDatabase);
test("EmailNewCustomerReport updates new customer flag", async () => {
await jobs.emailNewCustomerReport();
const res = await actions.getCustomer();
expect(res.reportSent).toBeTruthy();
});