prod-cron-safe
v1.0.0
Published
A production-ready wrapper over node-cron with retries, overlap prevention, and timeout support.
Maintainers
Readme
🛡️ Prod-Cron-Safe
A battle-tested, production-ready wrapper for node-cron. Stop worrying about overlapping tasks, thundering herds, or hung processes. Prod-Cron-Safe adds the resilience and observability your production environment demands.
Key Features
- Intelligent Retries: Exponential backoff with jitter to protect your services.
- Overlap Prevention: Ensure your tasks don't step on each other.
- Execution Timeouts: Automatically kill tasks that take too long.
- Fail-Fast Validation: Instantly validates cron expressions at schedule time.
- Task Registry: Monitor and manage all active jobs from a single interface.
- Graceful Shutdown: Automatic cleanup on
SIGINTandSIGTERM. - Rich Observability: Detailed execution history, durations, and lifecycle hooks.
- TypeScript First: Fully typed for a good developer experience.
📦 Installation
npm install prod-cron-safe🛠️ Basic Usage
import { scheduleProdCron } from "prod-cron-safe";
const job = scheduleProdCron("*/5 * * * *", async () => {
// Your mission-critical code here
console.log("Daily cleanup started...");
}, {
name: "DailyCleanup",
preventOverlap: true,
retries: 3
});
job.start();⚙️ Advanced Configuration (Options)
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| name | string | "unnamed-task" | Identification for logging and management. |
| preventOverlap | boolean | false | If true, skips execution if the task is already running. |
| executionTimeout| number | undefined | Timeout in ms. Kills the task if exceeded. |
| retries | number | 0 | Number of retry attempts on failure. |
| retryDelay | number | 1000 | Base delay in ms for retries. |
| maxRetryDelay | number | 30000 | Ceiling for exponential backoff (ms). |
| useExponentialBackoff | boolean | true | Enables exponential backoff (delay * 2^attempt). |
| jitter | boolean | true | Adds random noise to delay (prevents thundering herd). |
🪝 Lifecycle Hooks
Every hook receives a TaskContext containing the task ID, name, current attempt, and start time.
scheduleProdCron("0 0 * * *", myTask, {
onStart: (ctx) => console.log(`[${ctx.name}] Started (Attempt ${ctx.attempt})`),
onSuccess: (result, ctx) => {
console.log(`[${ctx.name}] Finished successfully in ${Date.now() - ctx.startTime.getTime()}ms`);
},
onError: (err, ctx) => {
console.error(`[${ctx.name}] Failed after all retries: ${err.message}`);
},
onRetry: (err, attempt, delay, ctx) => {
console.warn(`[${ctx.name}] Retry ${attempt} in ${Math.round(delay)}ms due to: ${err.message}`);
}
});📊 Management & Monitoring
Global Registry
Access all registered tasks to monitor their state or history.
import { getRegisteredTasks, stopAll } from "prod-cron-safe";
// get status of all tasks
const activeTasks = getRegisteredTasks();
activeTasks.forEach(task => {
console.log(`${task.name} (${task.id}) - Running: ${task.isRunning}`);
console.table(task.history); // Last 20 executions
});
// kill everything gracefully
stopAll();Manual Trigger
Need to run a task right now? Use .trigger() without waiting for the cron schedule.
const job = scheduleProdCron("0 0 * * *", cleanup);
await job.trigger(); // Runs manually, returns Promise with result🛡️ Production Best Practices
Graceful Shutdown
Prod-Cron-Safe automatically listens for SIGINT and SIGTERM to stop all jobs. This ensures your app exits cleanly without leaving dangling timers.
Error Handling
Always return a Promise from your task function. Rejections will trigger the retry logic automatically.
History Limits
For memory safety, only the last 20 execution records are kept per task.
📜 License
ISC © 2026
