v0.21

scheduling and commands

introduction

dframework includes a built in task scheduler that lets you define recurring background tasks directly in your application code. instead of configuring system level cron jobs on your server, you define your schedule in a single file and the framework handles the rest. tasks can be references to command classes or inline functions.

the scheduler starts automatically when the application boots and runs for the lifetime of the process. each task is executed in the main thread synchronously in an isolated context and automatically rescheduled after completion.

defining schedules

the schedule file

your schedule is defined in console/Schedule.js at the root of your project. this file exports a default function that receives the Scheduler instance. you use the scheduler's command() method to register tasks and chain a frequency method.

1export default function (scheduler) {
2 scheduler.command('CleanupExpiredSessions').daily();
3 scheduler.command('SyncExternalData').everyFifteenMinutes();
4 scheduler.command('GenerateSitemaps').weekly();
5}

the framework automatically detects this file on startup and begins executing the defined schedule.

scheduling commands

when you pass a string to scheduler.command(), the scheduler resolves it as a command file in your console/commands directory. the file must export a default class with a handle() method.

1scheduler.command('PruneOldLogs').daily();

this will look for console/commands/PruneOldLogs.js, instantiate the class, and call its handle() method at the specified interval.

scheduling closures

for simple tasks that don't warrant a dedicated command file, you can pass an inline function directly.

1export default function (scheduler) {
2 scheduler.command(async () => {
3 const count = await Session.where('expires_at', '<', new Date()).delete();
4 Log.info(`pruned ${count} expired sessions`);
5 }).everyThirtyMinutes();
6}

frequency options

preset intervals

the scheduler provides a set of human readable preset interval methods that you chain after command().

method interval
everyMinute() 60 seconds
everyFiveMinutes() 5 minutes
everyTenMinutes() 10 minutes
everyFifteenMinutes() 15 minutes
everyThirtyMinutes() 30 minutes
hourly() 1 hour
daily() 24 hours
weekly() 7 days
1scheduler.command('HeartbeatCheck').everyMinute();
2scheduler.command('CacheWarmer').hourly();
3scheduler.command('WeeklyDigest').weekly();

custom intervals

for intervals not covered by the presets, use the every() method with a duration in milliseconds.

1scheduler.command('PollExternalApi').every(45000);
2scheduler.command('RotateApiKeys').every(12 * 60 * 60 * 1000);

creating commands

the command class

a command is a class stored in the console/commands directory. it must export a default class with a handle() method. the handle method contains the logic that will be executed when the command runs.

1export default class PruneOldLogsCommand {
2 async handle() {
3 const cutoff = new Date();
4 cutoff.setDate(cutoff.getDate() - 30);
5 await DB.table('logs').where('created_at', '<', cutoff).delete();
6 Log.info('old logs pruned');
7 }
8}

commands used by the scheduler do not receive any arguments. if you need to pass data, use configuration values or database lookups inside the command itself.

generating commands

you can scaffold a new command file using the cli.

1dstrn make:command PruneOldLogs

this creates console/commands/PruneOldLogs.js with the correct boilerplate structure.

running commands

you can execute any command on demand using the run cli command.

1dstrn run PruneOldLogs

dframework imports the command class from console/commands/PruneOldLogs.js, instantiates it, calls handle(), and reports the elapsed time on completion.

how the scheduler works

automatic startup

when the application starts, the framework checks for a console/Schedule.js file. if it exists, the framework imports it, passes a new Scheduler instance, and calls scheduler.start() after all tasks are registered. each registered task is executed immediately on startup and then rescheduled based on its configured interval.

execution and rescheduling

when a task's interval elapses, the scheduler calls the task's handler (either a command class or an inline function). after the task completes, the scheduler calculates the next execution time by subtracting the elapsed execution time from the interval, ensuring consistent spacing between runs even if the task takes a significant amount of time to complete. the next run is scheduled using setTimeout.

each task tracks its lastRun timestamp. the scheduler uses this to compute the appropriate delay before the next execution.

manual execution

you can programmatically trigger any registered scheduled task by name using scheduler.run().

1await app.scheduler.run('CleanupExpiredSessions');

this immediately executes the task and resets its scheduling timer.

error handling

if a scheduled task throws an error, the scheduler catches it and logs the error using the application logger. the task is then rescheduled normally. a single failing task does not affect other scheduled tasks or the application's http server.

1[Scheduler] PruneOldLogs caused error: ER_NO_SUCH_TABLE: Table 'logs' doesn't exist

if the task itself causes an unexpected error during the scheduling phase, a separate catch logs it and prevents the error from propagating to the main process.