> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cron

> Schedule recurring background jobs with a human-readable time syntax, timezone awareness, and lifecycle control.

The `@ooneex/cron` component runs recurring background tasks. You extend the abstract `Cron` class, declare a schedule with a readable string like `"every 1 hours"`, and implement a `handler()` that runs on that schedule. Behind the scenes the schedule is converted to a crontab expression and driven by Bun's native `Bun.cron`, so there is no external scheduler to operate.

## Why this component

* **Human-readable schedules.** Express timing as `"every 5 minutes"` or `"in 30 seconds"` instead of raw crontab syntax — conversion is automatic.
* **One class per job.** Each job is a class that implements three methods: `getTime()`, `getTimeZone()`, and `handler()`.
* **Timezone aware.** Return an IANA timezone (e.g. `"Europe/Paris"`) or `null` to use the server's local time.
* **Lifecycle control.** Start, stop, and inspect a job with `start()`, `stop()`, and `isActive()`.
* **Container-managed.** Register a job with `@decorator.cron()` and resolve it from the container.

## How it works

A cron job is a class that extends `Cron` and implements its three abstract methods. When you call `start()`, the schedule from `getTime()` is converted to a crontab expression and registered with `Bun.cron`, which invokes your `handler()` on every tick.

| Method          | Purpose                                                                                                |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `getTime()`     | Return the schedule as a `CronTimeType` string, e.g. `"every 1 hours"`.                                |
| `getTimeZone()` | Return an IANA timezone string, or `null` for the server's local timezone.                             |
| `handler()`     | The async work to run on each scheduled tick.                                                          |
| `start()`       | Convert the schedule and register the job. No-op if already active. Throws `CronException` on failure. |
| `stop()`        | Stop the job and release the underlying timer.                                                         |
| `isActive()`    | Whether the job is currently scheduled.                                                                |

The schedule string has the shape `"<prefix> <number> <unit>"`:

| Part   | Values                                                   |
| ------ | -------------------------------------------------------- |
| Prefix | `every` (recurring) or `in` (one-time, after the delay)  |
| Number | Any positive integer                                     |
| Unit   | `seconds`, `minutes`, `hours`, `days`, `months`, `years` |

So `"every 5 minutes"` runs repeatedly every five minutes, while `"in 30 seconds"` schedules a single run thirty seconds from when the job starts. An invalid format or a non-positive number throws `CronException`.

## Decorator and usage

### `@decorator.cron()`

Registers a cron class with the container. It accepts an optional scope (defaults to singleton). Decorate any class that extends `Cron`.

```typescript theme={null}
import type { TimeZoneType } from "@ooneex/country";
import { Cron, decorator, type CronTimeType } from "@ooneex/cron";

@decorator.cron()
export class CleanupCron extends Cron {
  public getTime(): CronTimeType {
    return "every 1 hours";
  }

  public getTimeZone(): TimeZoneType | null {
    return null; // server's local timezone
  }

  public async handler(): Promise<void> {
    await this.removeExpiredRecords();
  }

  private async removeExpiredRecords(): Promise<void> {
    // cleanup logic
  }
}
```

Resolve and control the job from the container:

```typescript theme={null}
import { container } from "@ooneex/container";

const cleanup = container.get(CleanupCron);

await cleanup.start();
cleanup.isActive(); // true

await cleanup.stop();
cleanup.isActive(); // false
```

Run a job in a specific timezone by returning an IANA name from `getTimeZone()`:

```typescript theme={null}
import type { TimeZoneType } from "@ooneex/country";
import { Cron, decorator, type CronTimeType } from "@ooneex/cron";

@decorator.cron()
export class DailyReportCron extends Cron {
  public getTime(): CronTimeType {
    return "every 1 days";
  }

  public getTimeZone(): TimeZoneType | null {
    return "Europe/Paris";
  }

  public async handler(): Promise<void> {
    await this.generateDailyReport();
  }

  private async generateDailyReport(): Promise<void> {
    // report logic
  }
}
```

Use the `in` prefix for a one-time delayed run:

```typescript theme={null}
public getTime(): CronTimeType {
  return "in 30 seconds"; // runs once, 30 seconds after start()
}
```

## Exceptions

The component throws `CronException` when a schedule is malformed or a job fails to start. It carries a machine-readable `key`, a human-readable `message`, and a `data` object.

| Key              | When                                                                              |
| ---------------- | --------------------------------------------------------------------------------- |
| `INVALID_FORMAT` | The schedule string is not in `"<prefix> <number> <unit>"` form.                  |
| `INVALID_VALUE`  | The number in the schedule is not a positive integer.                             |
| `START_FAILED`   | The underlying scheduler rejected the converted crontab expression when starting. |

```typescript theme={null}
import { CronException } from "@ooneex/cron";

try {
  await job.start();
} catch (error) {
  if (error instanceof CronException) {
    logger.error(`Cron error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}
```

## Best practices

* **Catch errors inside `handler()`.** A throw in the handler should not take down the scheduler — wrap risky work and log failures.
* **Keep handlers idempotent.** A run may overlap or repeat; make the work safe to execute more than once.
* **Match the interval to the work.** Avoid scheduling a job more often than it can finish; a long task on `"every 1 minutes"` can pile up.
* **Pin a timezone for time-sensitive jobs.** Return an explicit IANA zone for reports and billing so behavior is stable across servers; use `null` only when local time is fine.
* **Resolve shared dependencies from the container.** Inject caches, databases, and loggers rather than constructing them inside the handler.
* **Use `in` for delayed one-shots, `every` for recurring work.** Pick the prefix that matches the intent instead of approximating with a long interval.
* **Stop jobs you no longer need.** Call `stop()` to release the timer; check `isActive()` before assuming a job is running.

## CLI command

Scaffold a cron class and its test file with the generator. It writes the class under `modules/<module>/src/crons/<Name>Cron.ts`, registers it in the module's `cronJobs` array, and installs `@ooneex/cron` if it is missing.

```bash theme={null}
# Interactive: prompts for the name
ooneex cron:create

# Provide the name
ooneex cron:create --name=Cleanup

# Target a module and overwrite
ooneex cron:create --name=Cleanup --module=auth --override
```

| Option       | Description                                                   | Default             |
| ------------ | ------------------------------------------------------------- | ------------------- |
| `--name`     | Cron class name. The `Cron` suffix is appended automatically. | Prompted if omitted |
| `--module`   | Target module the class is generated into.                    | `shared`            |
| `--override` | Overwrite an existing class without prompting.                | `false`             |

The generated class is a ready-to-fill stub with the schedule, timezone, and handler in place:

```typescript theme={null}
import type { TimeZoneType } from "@ooneex/country";
import type { CronTimeType } from "@ooneex/cron";
import { Cron, decorator } from "@ooneex/cron";

@decorator.cron()
export class CleanupCron extends Cron {
  public getTime(): CronTimeType {
    // Examples: "every 5 minutes", "every 1 hours", "every 30 seconds"
    return "every 1 hours";
  }

  public getTimeZone(): TimeZoneType | null {
    // Return null to use server timezone, or specify a timezone like "Europe/Paris"
    return null;
  }

  public async handler(): Promise<void> {
    // Implement your cron handler logic here
    // console.log("CleanupCron handler executed");
  }
}
```

See [cron:create](/cli/commands/cron-create) for the full command reference.

## Use with Claude and Codex

The generator ships a matching `cron:create` skill. It runs the scaffold and then guides your AI agent through completing the job — setting the schedule in `getTime()`, choosing a timezone, and implementing `handler()` with real logic. Initialize the skills once for your agent:

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    ooneex claude:init
    ```

    Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create a cron job that runs every night at midnight to clean up expired sessions.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    ooneex codex:init
    ```

    Then ask Codex in natural language — it maps the request to the generator, runs it, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create a cron job that runs every night at midnight to clean up expired sessions.
    ```
  </Tab>
</Tabs>

For example, the prompt above maps to `cron:create --name=CleanExpiredSessions`, then sets the schedule and implements the `handler()` to remove expired sessions.
