feat: crons for all bin scripts, new jobs:handle-schedules script
This commit is contained in:
@@ -318,3 +318,13 @@ Now you can run the command using:
|
||||
```sh
|
||||
pnpm payload seed
|
||||
```
|
||||
|
||||
## Running bin scripts on a schedule
|
||||
|
||||
Every bin script supports being run on a schedule using cron syntax. Simply pass the `--cron` flag followed by the cron expression when running the script. Example:
|
||||
|
||||
```sh
|
||||
pnpm payload run ./myScript.ts --cron "0 * * * *"
|
||||
```
|
||||
|
||||
This will use the `run` bin script to execute the specified script on the defined schedule.
|
||||
|
||||
@@ -173,25 +173,31 @@ const results = await payload.jobs.runByID({
|
||||
Finally, you can process jobs via the bin script that comes with Payload out of the box. By default, this script will run jobs from the `default` queue, with a limit of 10 jobs per invocation:
|
||||
|
||||
```sh
|
||||
npx payload jobs:run
|
||||
pnpm payload jobs:run
|
||||
```
|
||||
|
||||
You can override the default queue and limit by passing the `--queue` and `--limit` flags:
|
||||
|
||||
```sh
|
||||
npx payload jobs:run --queue myQueue --limit 15
|
||||
pnpm payload jobs:run --queue myQueue --limit 15
|
||||
```
|
||||
|
||||
If you want to run all jobs from all queues, you can pass the `--all-queues` flag:
|
||||
|
||||
```sh
|
||||
npx payload jobs:run --all-queues
|
||||
pnpm payload jobs:run --all-queues
|
||||
```
|
||||
|
||||
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:
|
||||
|
||||
```sh
|
||||
npx payload jobs:run --cron "*/5 * * * *"
|
||||
pnpm payload jobs:run --cron "*/5 * * * *"
|
||||
```
|
||||
|
||||
You can also pass `--handle-schedules` flag to the `jobs:run` command to make it schedule jobs according to configured schedules:
|
||||
|
||||
```sh
|
||||
pnpm payload jobs:run --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
|
||||
```
|
||||
|
||||
## Processing Order
|
||||
|
||||
@@ -35,6 +35,20 @@ Something needs to actually trigger the scheduling of jobs (execute the scheduli
|
||||
|
||||
You can disable this behavior by setting `disableScheduling: true` in your `autorun` configuration, or by passing `disableScheduling=true` to the `/api/payload-jobs/run` endpoint. This is useful if you want to handle scheduling manually, for example, by using a cron job or a serverless function that calls the `/api/payload-jobs/handle-schedules` endpoint or the `payload.jobs.handleSchedules()` local API method.
|
||||
|
||||
### Bin Scripts
|
||||
|
||||
Payload provides a set of bin scripts that can be used to handle schedules. If you're already using the `jobs:run` bin script, you can set it to also handle schedules by passing the `--handle-schedules` flag:
|
||||
|
||||
```sh
|
||||
pnpm payload jobs:run --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
|
||||
```
|
||||
|
||||
If you only want to handle schedules, you can use the dedicated `jobs:handle-schedules` bin script:
|
||||
|
||||
```sh
|
||||
pnpm payload jobs:handle-schedules --queue myQueue # or --all-queues
|
||||
```
|
||||
|
||||
## Defining schedules on Tasks or Workflows
|
||||
|
||||
Schedules are defined using the `schedule` property:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-strict-ignore
|
||||
/* eslint-disable no-console */
|
||||
import { Cron } from 'croner'
|
||||
import minimist from 'minimist'
|
||||
@@ -6,7 +5,7 @@ import { pathToFileURL } from 'node:url'
|
||||
import path from 'path'
|
||||
|
||||
import { findConfig } from '../config/find.js'
|
||||
import payload, { getPayload } from '../index.js'
|
||||
import { getPayload, type Payload } from '../index.js'
|
||||
import { generateImportMap } from './generateImportMap/index.js'
|
||||
import { generateTypes } from './generateTypes.js'
|
||||
import { info } from './info.js'
|
||||
@@ -20,19 +19,54 @@ const availableScripts = [
|
||||
'generate:types',
|
||||
'info',
|
||||
'jobs:run',
|
||||
'jobs:handle-schedules',
|
||||
'run',
|
||||
...migrateCommands,
|
||||
] as const
|
||||
|
||||
export const bin = async () => {
|
||||
loadEnv()
|
||||
process.env.DISABLE_PAYLOAD_HMR = 'true'
|
||||
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const script = (typeof args._[0] === 'string' ? args._[0] : '').toLowerCase()
|
||||
|
||||
if (args.cron) {
|
||||
new Cron(args.cron, async () => {
|
||||
// If the bin script initializes payload (getPayload), this will only happen once, as getPayload
|
||||
// caches the payload instance on the module scope => no need to manually cache and manage getPayload initialization
|
||||
// outside the Cron here.
|
||||
await runBinScript({ args, script })
|
||||
})
|
||||
|
||||
process.stdin.resume() // Keep the process alive
|
||||
|
||||
return
|
||||
} else {
|
||||
const { payload } = await runBinScript({ args, script })
|
||||
if (payload) {
|
||||
await payload.destroy() // close database connections after running jobs so process can exit cleanly
|
||||
}
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
async function runBinScript({
|
||||
args,
|
||||
script,
|
||||
}: {
|
||||
args: minimist.ParsedArgs
|
||||
script: string
|
||||
}): Promise<{
|
||||
/**
|
||||
* Scripts can return a payload instance if it exists. The bin script runner can then safely
|
||||
* shut off the instance, depending on if it's running in a cron job or not.
|
||||
*/
|
||||
payload?: Payload
|
||||
}> {
|
||||
if (script === 'info') {
|
||||
await info()
|
||||
return
|
||||
return {}
|
||||
}
|
||||
|
||||
if (script === 'run') {
|
||||
@@ -58,7 +92,7 @@ export const bin = async () => {
|
||||
// Restore original process.argv
|
||||
process.argv = originalArgv
|
||||
}
|
||||
return
|
||||
return {}
|
||||
}
|
||||
|
||||
const configPath = findConfig()
|
||||
@@ -91,19 +125,22 @@ export const bin = async () => {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
return
|
||||
return {}
|
||||
}
|
||||
|
||||
if (script.startsWith('migrate')) {
|
||||
return migrate({ config, parsedArgs: args }).then(() => process.exit(0))
|
||||
await migrate({ config, parsedArgs: args })
|
||||
return {}
|
||||
}
|
||||
|
||||
if (script === 'generate:types') {
|
||||
return generateTypes(config)
|
||||
await generateTypes(config)
|
||||
return {}
|
||||
}
|
||||
|
||||
if (script === 'generate:importmap') {
|
||||
return generateImportMap(config)
|
||||
await generateImportMap(config)
|
||||
return {}
|
||||
}
|
||||
|
||||
if (script === 'jobs:run') {
|
||||
@@ -111,45 +148,47 @@ export const bin = async () => {
|
||||
const limit = args.limit ? parseInt(args.limit, 10) : undefined
|
||||
const queue = args.queue ? args.queue : undefined
|
||||
const allQueues = !!args.allQueues
|
||||
const handleSchedules = !!args.handleSchedules
|
||||
|
||||
if (args.cron) {
|
||||
new Cron(args.cron, async () => {
|
||||
await payload.jobs.run({
|
||||
allQueues,
|
||||
limit,
|
||||
queue,
|
||||
})
|
||||
})
|
||||
|
||||
process.stdin.resume() // Keep the process alive
|
||||
|
||||
return
|
||||
} else {
|
||||
await payload.jobs.run({
|
||||
if (handleSchedules) {
|
||||
await payload.jobs.handleSchedules({
|
||||
allQueues,
|
||||
limit,
|
||||
queue,
|
||||
})
|
||||
|
||||
await payload.destroy() // close database connections after running jobs so process can exit cleanly
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
await payload.jobs.run({
|
||||
allQueues,
|
||||
limit,
|
||||
queue,
|
||||
})
|
||||
|
||||
return { payload }
|
||||
}
|
||||
|
||||
if (script === 'jobs:handle-schedules') {
|
||||
const payload = await getPayload({ config }) // Do not setup crons here - this bin script can set up its own crons
|
||||
const queue = args.queue ? args.queue : undefined
|
||||
const allQueues = !!args.allQueues
|
||||
|
||||
await payload.jobs.handleSchedules({
|
||||
allQueues,
|
||||
queue,
|
||||
})
|
||||
|
||||
return { payload }
|
||||
}
|
||||
|
||||
if (script === 'generate:db-schema') {
|
||||
// Barebones instance to access database adapter, without connecting to the DB
|
||||
await payload.init({
|
||||
config,
|
||||
disableDBConnect: true,
|
||||
disableOnInit: true,
|
||||
})
|
||||
const payload = await getPayload({ config, disableDBConnect: true, disableOnInit: true }) // Do not setup crons here
|
||||
|
||||
if (typeof payload.db.generateSchema !== 'function') {
|
||||
payload.logger.error({
|
||||
msg: `${payload.db.packageName} does not support database schema generation`,
|
||||
})
|
||||
|
||||
await payload.destroy()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -158,7 +197,7 @@ export const bin = async () => {
|
||||
prettify: args.prettify === 'false' ? false : true,
|
||||
})
|
||||
|
||||
process.exit(0)
|
||||
return { payload }
|
||||
}
|
||||
|
||||
console.error(script ? `Unknown command: "${script}"` : 'Please provide a command to run')
|
||||
|
||||
@@ -1010,9 +1010,7 @@ export const reload = async (
|
||||
;(global as any)._payload_doNotCacheClientSchemaMap = true
|
||||
}
|
||||
|
||||
export const getPayload = async (
|
||||
options: Pick<InitOptions, 'config' | 'cron' | 'importMap'>,
|
||||
): Promise<Payload> => {
|
||||
export const getPayload = async (options: InitOptions): Promise<Payload> => {
|
||||
if (!options?.config) {
|
||||
throw new Error('Error: the payload config is required for getPayload to work.')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user