@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(), andhandler(). - Timezone aware. Return an IANA timezone (e.g.
"Europe/Paris") ornullto use the server’s local time. - Lifecycle control. Start, stop, and inspect a job with
start(),stop(), andisActive(). - Container-managed. Register a job with
@decorator.cron()and resolve it from the container.
How it works
A cron job is a class that extendsCron 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. |
"<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 |
"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.
getTimeZone():
in prefix for a one-time delayed run:
Exceptions
The component throwsCronException 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. |
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
nullonly when local time is fine. - Resolve shared dependencies from the container. Inject caches, databases, and loggers rather than constructing them inside the handler.
- Use
infor delayed one-shots,everyfor 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; checkisActive()before assuming a job is running.
CLI command
Scaffold a cron class and its test file with the generator. It writes the class undermodules/<module>/src/crons/<Name>Cron.ts, registers it in the module’s cronJobs array, and installs @ooneex/cron if it is missing.
| 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 |
Use with Claude and Codex
The generator ships a matchingcron: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:
- Claude
- Codex
Prompt
cron:create --name=CleanExpiredSessions, then sets the schedule and implements the handler() to remove expired sessions.