k.jobs
Declare your API's scheduled jobs, with their schedules, inputs, and results.
Jobs are new and still settling. k.jobs may change while v2 is in beta, so pin your version if you depend on it.
Declares your API's scheduled jobs. The first argument is the identity every job requires, the credential your scheduler sends. Pass the result to defineConfig under jobs, alongside routes.
import { z } from 'zod';
import { cron } from 'kizunajs';
export const jobs = k.jobs('scheduler', {
sendDigests: {
schedule: cron.daily('05:00'),
summary: 'Send the daily digest to every user',
result: z.object({
sent: z.int(),
}),
},
billing: {
reconcileInvoices: {
schedule: cron.every('15m'),
input: z.object({
since: z.string(),
}),
},
},
});Jobs nest to any depth. Because a job carries its own identity, it declares no auth of its own.
Omitting the identity argument declares the jobs public, which leaves anyone who can reach /jobs/run able to trigger them. Pass an identity unless the endpoints are unreachable from outside your network.
See the Scheduled jobs guide for handlers, triggering, and local development.
Job fields
| Field | Type | Notes |
|---|---|---|
schedule | string | { cron, timezone } | A five-field cron expression, UTC unless a timezone is given. Omit it for a job that is only ever queued from code. |
retry | number | How many attempts a failed run deserves, at least 1. Handed to the transport, which is what retries. Defaults to 1. |
input | z.ZodType | Schema for the payload the job is queued with. Validated on run and queue, and again at the run endpoint. |
result | z.ZodType | Schema for what the job reports on success. Omit it and the job answers 204. |
summary | string | |
description | string | |
responses | { [status]: ResponseDefinition } | Extra responses beyond the synthesized ones. |
Synthesized responses
Every job answers with the same set. A scheduler reads the status code to decide whether to retry.
| Status | Meaning |
|---|---|
200 with result, or 204 without | Done |
422 | Permanent failure; do not retry |
500 | Unexpected throw; retry |
503 | Transient failure; retry |
422, 500, and 503 take an RFC 9457 Problem Details body, so a handler supplies detail plus any extension members.
Schedule helpers
Exported from kizunajs. Each returns a plain cron string.
| Helper | Expression |
|---|---|
cron.every('15m') | */15 * * * * |
cron.every('2h') | 0 */2 * * * |
cron.hourly(30) | 30 * * * * |
cron.daily('05:00') | 0 5 * * * |
cron.weekly('mon', '09:00') | 0 9 * * 1 |
cron.monthly(1, '05:00') | 0 5 1 * * |
nextRun(schedule, from?) returns the next time a schedule fires, or undefined for a schedule that never can (0 0 30 2 *). nextRuns(schedule, count, from?) returns several.
Reaching a job
Every handler receives a jobs runner shaped like the declaration, with two methods per job. run resolves to what the handler returned, as { status, body }; queue resolves once the job is in line.
const result = await jobs.billing.reconcileInvoices.run({ since });
await jobs.indexUser.queue({
input: {
userId,
},
dedupeKey: `index:${userId}`,
runAt: tomorrow,
});Input is validated against the job's input schema either way. Where a queued job goes is the transport's business; with none configured it runs in this process.
Validation
k.jobs throws on:
- an invalid cron expression, naming the field
- an unknown time zone
- a
retrythat is not a whole number, or is below1 - a job with a
scheduleand aninputits schema will not accept as empty. A scheduler sends no body, so that job could only ever fail validation