@azlib/scheduler
v1.2.2
Published
Cron-style job scheduler for Node.js, supporting timezone normalization, overlap prevention policies, missed run executions, and database persistence.
Readme
@azlib/scheduler
Cron-style job scheduler for Node.js, supporting timezone normalization, overlap prevention policies, missed run executions, and database persistence.
Capabilities
- Schedule parsing and timezone normalization
- Job registration and lifecycle control (start, stop)
- Standalone runner execution or embedded hosting (e.g. Express)
- Host lifecycle bindings for graceful setups
- Queue-backed execution dispatch through
@azlib/queue - Cache coordination using
@azlib/cache - Structured database state persistence (SQL persistence)
- Operator dashboard (HTTP + React UI) for monitoring and controlling jobs
AI Agent Quick Reference
Core Exports
| Export | Type | Description |
| ------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------- |
| createSchedulerService(options: SchedulerServiceOptions) | Function | Instantiates a SchedulerService instance and a Handler Registry. |
| createCronExpression(): CronExpressionBuilder | Function | Fluent helper to construct standard 5-field cron strings. |
| bindSchedulerToHost(service: SchedulerService, adapter: SchedulerHostAdapter) | Function | Automatically starts/stops the scheduler based on custom server bindings. |
| CronWeekday | Enum | Monday through Sunday utility enum values. |
| createSchedulerDashboardService(scheduler, options?) | Function | Operator API: health, list, pause/resume, run-now, retry, CRUD. |
| createSchedulerDashboardFromPersistence(persistence) | Function | Sidecar dashboard against the same SQL tables (does not start the engine). |
| createSchedulerDashboardServer(options) | Function | Serves the React UI and JSON API (@azlib/scheduler/dashboard). |
| createSchedulerDashboardNodeHandler(options) | Function | Same monitor as the standalone server, for Express/http hosts. |
Dashboard
The dashboard can run in-process next to a live SchedulerService, or as a sidecar / Docker process that shares SQL persistence. The sidecar never starts a second scheduler engine. Pause/resume writes enabled on the job row; the worker re-reads that on every tick.
import {
createSchedulerDashboardServer,
createSchedulerDashboardService,
createSchedulerDashboardNodeHandler,
} from "@azlib/scheduler/dashboard";
const dashboard = createSchedulerDashboardService(service, { handlers });
const server = createSchedulerDashboardServer({
dashboard,
port: 9100,
host: "127.0.0.1",
});
await server.listen();
// Or mount the same monitor on an existing Node/Express server:
app.use(
createSchedulerDashboardNodeHandler({
dashboard,
basePath: "/scheduler",
authorize: async ({ authorization }) => {
// throw DashboardHttpError(401 | 403, message) to reject
},
}),
);Sidecar CLI (azlib-scheduler-dashboard) and Docker (packages/scheduler/Dockerfile, compose.yaml):
| Env | Default | Notes |
| --------------------- | ---------------------------------- | ------------------------------------------------ |
| DATABASE_URL | required | Same database as the worker |
| SCHEDULER_DIALECT | inferred from URL | postgres (default), mysql, sqlite, mssql |
| SCHEDULER_NAMESPACE | azlib | Table prefix (azlib__scheduler_jobs, …) |
| HOST | 127.0.0.1 | Use 0.0.0.0 in Docker |
| PORT | 9100 | |
| DASHBOARD_TOKEN | required when HOST is not loopback | Authorization: Bearer … on /api/* |
Install the matching SQL driver (pg, mysql2, better-sqlite3, or mssql) next to @azlib/scheduler. Run-now and retry enqueue only when the worker’s queue is reachable; pause, resume, update, and delete work from SQL alone.
DASHBOARD_TOKEN=secret docker compose -f packages/scheduler/compose.yaml up --buildCore Types & Signatures
SchedulerService:start(): Promise<void>stop(): Promise<void>registerJob(job: SchedulerJobDefinition): Promise<void>unregisterJob(jobName: string): Promise<void>
SchedulerHandlerRegistry:register(key: string, handler: (config?: any) => Promise<void>): void
SchedulerJobDefinition:name: stringhandlerKey: stringschedule: SchedulerScheduleConfigconfig?: any(JSON-serializable config injected into handler)
SchedulerScheduleConfig:scheduleType: "cron"expression: stringtimezone?: string(e.g."UTC","Asia/Saigon")overlapPolicy: "allow" | "skip" | "enqueue"missedRunPolicy: "run-immediately" | "skip"
Basic Usage
import { createQueueService } from "@azlib/queue";
import { createSchedulerService, createCronExpression } from "@azlib/scheduler";
const queue = createQueueService({/* ... */});
const { service, handlers } = createSchedulerService({
mode: "standalone",
queueService: queue,
});
// 1. Register executor logic
handlers.register("purge-logs", async (config: { thresholdDays: number }) => {
await db.logs.deleteOlderThan(config.thresholdDays);
});
// 2. Register scheduled trigger
await service.registerJob({
name: "nightly-purge",
handlerKey: "purge-logs",
schedule: {
scheduleType: "cron",
expression: createCronExpression().dailyAt(2, 30).build(), // 02:30 AM
timezone: "UTC",
overlapPolicy: "skip",
missedRunPolicy: "run-immediately",
},
config: { thresholdDays: 30 },
});
// 3. Boot scheduler
await service.start();Behavioral Gotchas
- Timezone Safety: Always specify a
timezonein job configs to prevent local developer clocks from altering trigger cycles. - Overlap Policies:
skip: If a previous job execution is still running, the new scheduled trigger is skipped.enqueue: Queues the new run to execute immediately after the active run completes.allow: Runs execution concurrent to existing instances (warning: potential race conditions).
- Execution Engine: Jobs are not run inside the scheduler thread directly. Instead, they are dispatched as tasks to the configured
@azlib/queueprovider to preserve single-thread event loop safety.
