> ## Documentation Index
> Fetch the complete documentation index at: https://stars-components.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# @wolfstar/plugin-scheduled-tasks

> Schedule one-off and repeated tasks for HTTP Framework bots, stored in Redis and run by BullMQ.

```bash npm theme={"system"}
npm install @wolfstar/plugin-scheduled-tasks @wolfstar/kit
```

```bash pnpm theme={"system"}
pnpm add @wolfstar/plugin-scheduled-tasks @wolfstar/kit
```

```bash yarn theme={"system"}
yarn add @wolfstar/plugin-scheduled-tasks @wolfstar/kit
```

```bash bun theme={"system"}
bun add @wolfstar/plugin-scheduled-tasks @wolfstar/kit
```

> [card] [npm](https://npmx.dev/package/@wolfstar/plugin-scheduled-tasks)

> [card] [Source](https://github.com/wolfstar-project/plugins/tree/main/packages/plugin-scheduled-tasks)

> [!TIP]
>
> See the [Plugins guide](https://stars-components.js.org/documentation/guide/plugins.md) for how plugins from wolfstar-project/plugins fit alongside the core framework.

## Description

`@wolfstar/plugin-scheduled-tasks` runs tasks later or on a schedule, such as unmuting a member in an hour or sending a
report every morning. It is the counterpart of
[`@sapphire/plugin-scheduled-tasks`](https://www.npmjs.com/package/@sapphire/plugin-scheduled-tasks) for
[`@wolfstar/http-framework`](https://stars-components.js.org/documentation/packages/http-framework).

Tasks are pieces. [BullMQ](https://docs.bullmq.io/) stores their jobs in Redis, so jobs survive a restart and every
process of the bot shares them.

The plugin requires `@wolfstar/http-framework` 6.1.0 or later and a Redis server.

## Registering the plugin

With the `stars` CLI, list the package in `modules` and set its options under the `scheduledTasks` key. It needs the
optional [`@wolfstar/kit`](https://stars-components.js.org/documentation/packages/kit) peer dependency, at `^0.2.0 || ^0.3.0`.

```ts stars.config.ts theme={"system"}
import { defineConfig } from '@wolfstar/http-framework/config';
import '@wolfstar/plugin-scheduled-tasks';

export default defineConfig({
	modules: ['@wolfstar/plugin-scheduled-tasks'],
	scheduledTasks: { queue: 'tasks', bull: { connection: { host: 'localhost', port: 6379 } } }
});
```

The module reads its options from the `scheduledTasks` key of `stars.config`. The `[specifier, options]` tuple of `modules`
still works. Its options are merged over the `scheduledTasks` key, so they win.

The optional [`@wolfstar/schema`](https://stars-components.js.org/documentation/packages/schema) peer dependency types the `scheduledTasks` key in `defineConfig`.
TypeScript only loads that type once the module is imported, so import `@wolfstar/plugin-scheduled-tasks` in `stars.config.ts`.

The CLI writes options from `stars.config` into the built entry, so they must be JSON-serialisable.

Without the CLI, or to pass an `ioredis` instance, give the plugin to the client:

```ts theme={"system"}
import { Client } from '@wolfstar/http-framework';
import { scheduledTasks } from '@wolfstar/plugin-scheduled-tasks';

const client = new Client({
	plugins: [scheduledTasks({ bull: { connection: { host: 'localhost', port: 6379 } } })]
});
```

`ClientOptions.tasks` is merged over the plugin's options, so you can also set the connection there:

```ts theme={"system"}
import { Client } from '@wolfstar/http-framework';
import { scheduledTasks } from '@wolfstar/plugin-scheduled-tasks';
import { Redis } from 'ioredis';

const client = new Client({
	plugins: [scheduledTasks()],
	tasks: {
		bull: { connection: new Redis(process.env.REDIS_URL!, { maxRetriesPerRequest: null }) }
	}
});
```

Use either `modules` or `plugins`, not both. Registering the plugin twice runs its hooks twice and starts two workers on
the same queue. The `@wolfstar/plugin-scheduled-tasks/register` entrypoint exists for the CLI and does nothing.

If no `bull.connection` is set in either place, the plugin throws a `TypeError` when the client initializes.

| Option | Default | Description |
| - | - | - |
| `bull` | — | BullMQ's `QueueOptions`. `bull.connection` is required and the worker also uses it. |
| `queue` | `"scheduled-tasks"` | The name of the BullMQ queue. |
| `loadErrorListeners` | `true` | Load the listeners that log task and connection errors through `container.logger`. |
| `ready` | — | A function that tells whether the app is ready, for tasks that wait for it. See [Waiting for the app to be ready](#waiting-for-the-app-to-be-ready). |
| `readyTimeout` | `30000` | How long a job waits for `ready`, in milliseconds. |
| `readyDelay` | `30000` | How long a job that timed out waiting for `ready` is pushed back, in milliseconds. |

In `ClientOptions`, `tasks` accepts `bull`, `queue`, `ready`, `readyTimeout`, and `readyDelay`, and `loadScheduledTaskErrorListeners` overrides
`loadErrorListeners`.

## Writing a task

A task is a piece of the `scheduled-tasks` store, placed in the `scheduled-tasks` directory:

```ts src/scheduled-tasks/unmute.ts theme={"system"}
import { ScheduledTask } from '@wolfstar/plugin-scheduled-tasks';

export class UnmuteTask extends ScheduledTask<'unmute'> {
	public override async run(payload: { guildId: string; userId: string }) {
		// ...
	}
}

declare module '@wolfstar/plugin-scheduled-tasks' {
	interface ScheduledTasks {
		unmute: { guildId: string; userId: string };
	}
}
```

The `ScheduledTasks` interface types the payload everywhere. Use `never` for a task without a payload, and a union with
`undefined` for an optional one.

## Scheduling a task

```ts theme={"system"}
import { container } from '@wolfstar/http-framework';

// Run in one hour
await container.tasks.create({ name: 'unmute', payload: { guildId, userId } }, 3_600_000);
```

`create` returns the BullMQ job. The second argument is either a delay in milliseconds or an options object with
`delay`, `interval`, or `pattern` and `timezone`, plus optional `customJobOptions`.

| Method | Description |
| - | - |
| `create(task, options?)` | Schedules a task and returns its BullMQ job. |
| `get(id)` | Returns a job by its ID. |
| `list({ types })` | Lists jobs in the given BullMQ states. |
| `delete(id)` | Removes a job. It does not stop a repeated task. |
| `listRepeated()` | Lists the repeated tasks' job schedulers. |
| `deleteRepeated(name)` | Removes the job scheduler of a repeated task. |
| `waitForReady(timeout?)` | Waits for `ready` and resolves to whether the app is ready. |
| `close()` | Closes the queue and waits for the worker to finish its task. |

`container.tasks.client` is the BullMQ `Queue` itself.

## Repeated tasks

A task with an `interval` in milliseconds or a cron `pattern` is scheduled when the client starts listening:

```ts src/scheduled-tasks/report.ts theme={"system"}
import { ScheduledTask } from '@wolfstar/plugin-scheduled-tasks';

export class ReportTask extends ScheduledTask<'report'> {
	public constructor(context: ScheduledTask.LoaderContext) {
		super(context, { pattern: '0 8 * * *', timezone: 'Europe/Rome' });
	}

	public override run() {
		// Every day at 08:00
	}
}

declare module '@wolfstar/plugin-scheduled-tasks' {
	interface ScheduledTasks {
		report: never;
	}
}
```

`timezone` defaults to `UTC`. A repeated task is a BullMQ [job scheduler](https://docs.bullmq.io/guide/job-schedulers)
named after the task. Changing the `pattern` or `interval` in your code replaces its schedule on the next start.

Deleting the piece does not delete the scheduler from Redis. Remove it with `container.tasks.deleteRepeated('report')`,
and list existing schedulers with `container.tasks.listRepeated()`.

## Waiting for the app to be ready

When the worker starts, it immediately takes any job that came due while the bot was down. That can happen before the
app has what the task needs, such as the gateway cache. Use the `ready` option to tell the plugin when the app is ready,
and set `waitForReady: true` on the tasks that depend on it.

`ready` returns `true`, or a promise that resolves to anything but `false`, when the app is ready:

```ts theme={"system"}
import { Client, container } from '@wolfstar/http-framework';
import { scheduledTasks } from '@wolfstar/plugin-scheduled-tasks';

const client = new Client({
	plugins: [
		scheduledTasks({
			bull: { connection: { host: 'localhost', port: 6379 } },
			ready: () => container.gatewayClient.isClientReady(),
			readyTimeout: 30_000,
			readyDelay: 30_000
		})
	]
});
```

```ts src/scheduled-tasks/poststats.ts theme={"system"}
import { ScheduledTask } from '@wolfstar/plugin-scheduled-tasks';

export class PostStatsTask extends ScheduledTask<'poststats'> {
	public constructor(context: ScheduledTask.LoaderContext) {
		super(context, { pattern: '*/10 * * * *', waitForReady: true });
	}

	public override run() {
		// Runs only once the app is ready
	}
}
```

Before a `waitForReady` task runs, its job waits up to `readyTimeout` for `ready`. If `ready` returns `false`, the
plugin calls it again every 250 ms until the timeout. If the app is still not ready, the plugin moves the job back to
`delayed` for `readyDelay` and the client emits `scheduledTaskNotReady`. The delayed job costs no attempt and does not
emit `scheduledTaskError`. If `ready` throws or rejects, the job fails like a task error.

* Tasks without `waitForReady` never wait. `waitForReady` defaults to `false`.
* `waitForReady` does nothing if you don't set `ready`.
* The worker runs one job at a time, so a waiting job holds back the other tasks for up to `readyTimeout`. Lower it if
  that matters.

To wait inside a task by hand, call `container.tasks.waitForReady()`. It resolves to `true` once the app is ready, or
`false` if it still isn't after the timeout. It defaults to `readyTimeout`, and resolves to `true` immediately without a
`ready` option:

```ts theme={"system"}
if (!(await container.tasks.waitForReady(10_000))) return;
```

> [!WARNING]
>
> `ready` is a function, so you can't set it in `stars.config`. Pass it to `scheduledTasks()` or `ClientOptions.tasks` instead. You can still set
> `readyTimeout` and `readyDelay` in `stars.config`.

## Events

The client emits these events, whose names are in `ScheduledTaskEvents`:

* `scheduledTaskRun`, `scheduledTaskSuccess`, `scheduledTaskError`, `scheduledTaskFinished`, and
  `scheduledTaskNotFound` around every run.
* `scheduledTaskNotReady` when a task that waits for the app is delayed because the app isn't ready. The plugin installs
  no listener for it, so add one if you want to log it.
* `scheduledTaskStrategyConnectError`, `scheduledTaskStrategyClientError`, and `scheduledTaskStrategyWorkerError` for
  Redis and BullMQ failures.

A task that throws fails its job. BullMQ retries the job according to its `attempts`, which you set with
`customJobOptions`.

## Shutting down

```ts theme={"system"}
await container.tasks.close();
```

## Differences from `@sapphire/plugin-scheduled-tasks`

* It runs on BullMQ 6, where repeated tasks are job schedulers. `listRepeated()` returns `JobSchedulerJson[]`, and
  `deleteRepeated(name)` is new.
* The worker starts once the pieces are loaded rather than when the handler is constructed. A job is never taken before
  its task exists.
* `ClientOptions.tasks` is optional, since you can give the options to the plugin instead.

[![version](https://npmx.dev/api/registry/badge/version/@wolfstar/plugin-scheduled-tasks)](https://npmx.dev/package/@wolfstar/plugin-scheduled-tasks)
[![downloads](https://npmx.dev/api/registry/badge/downloads/@wolfstar/plugin-scheduled-tasks)](https://npmx.dev/package/@wolfstar/plugin-scheduled-tasks)
[![license](https://img.shields.io/github/license/wolfstar-project/plugins?style=flat-square\&color=informational)](https://github.com/wolfstar-project/plugins/blob/main/LICENSE)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.