@ramplex/clujo v5.0.0
@ramplex/clujo
IMPORTANT: clujo is now published under @ramplex/clujo
on npm and jsr. If you are using the old clujo
package, please update your dependencies to use @ramplex/clujo
instead. All future versions will be published under the new package name.
Clujo is a simple and flexible solution for running any object with a trigger
method on a cron schedule, with built-in support for preventing overlapping executions in distributed environments.
It would not be possible without the amazing work of the following projects:
- Croner: used for cron scheduling
- ioredis - (not a dependency, but the supported redis client) used to ensure single execution in a clustered/distributed environment
- redis-semaphore (only used if an
ioredis
instance is provided) - used to ensure single execution in a distributed environment
Coming soon: validated bun support.
Table of Contents
- Features
- Installation
- Quick Start
- Core Concepts
- Advanced Usage
- Using the Scheduler
- Examples
- Contributing
- License
Features
- Simple API: Just provide any object with a
trigger
function and a cron schedule - No Overlapping Executions: Built-in protection against concurrent runs of the same job
- Distributed Locking: Optional Redis integration for preventing overlaps across multiple instances
- Flexible Scheduling: Support for standard cron patterns, multiple patterns, and one-time executions
- Type Safety: Full TypeScript support with generic types for your trigger return values
- Graceful Shutdown: Clean stop mechanism that waits for running jobs to complete
- Manual Triggering: Execute jobs on-demand outside of their schedule
- Startup Execution: Option to run immediately when starting
- Job Management: Built-in Scheduler class for managing multiple Clujo instances
- Minimal Dependencies: Focused on doing one thing well
Installation
Clujo is available on jsr and npm, and supports Node.js and Deno v2.0.0 or later.
npm registry
Install Clujo using npm, pnpm, yarn:
npm install @ramplex/clujo
yarn add @ramplex/clujo
pnpm install @ramplex/clujo
jsr registry
deno
deno add jsr:@ramplex/clujo
npm (one of the below, depending on your package manager)
npx jsr add @ramplex/clujo
yarn dlx jsr add @ramplex/clujo
pnpm dlx jsr add @ramplex/clujo
Quick Start
Here's a simple example to get you started with Clujo:
import { Clujo } from '@ramplex/clujo';
// or (in node.js)
// const { Clujo } = require('@ramplex/clujo');
// Create any object with a trigger function
const myRunner = {
trigger: async () => {
console.log("Running my scheduled task!");
// Do your work here
const result = await performSomeWork();
return result;
}
};
// Create a Clujo instance
const clujo = new Clujo({
id: "my-scheduled-job",
cron: {
pattern: "*/5 * * * *", // Every 5 minutes
options: { tz: "America/New_York" }
},
runner: myRunner,
// Optional: prevent overlapping executions in distributed environments
redis: { client: redisClient },
// Optional: run immediately on startup
runOnStartup: true,
});
// Start the job
clujo.start();
// Manually trigger if needed
const result = await clujo.trigger();
// Gracefully stop
await clujo.stop();
Core Concepts
The Runner Interface
Clujo works with any object that implements a simple interface:
interface IRunner<T> {
trigger: () => T | Promise<T>;
}
This means you can schedule anything - from simple functions wrapped in an object to complex workflow systems, database maintenance tasks, or API polling jobs.
Preventing Overlapping Executions
By default, Clujo prevents overlapping executions on a single instance. If a job is still running when the next scheduled execution arrives, the new execution is skipped. This behavior is automatic and requires no configuration.
For distributed environments with multiple instances, you can provide a Redis client to ensure only one instance executes the job at any given time across your entire system.
Advanced Usage
Multiple Cron Patterns
You can specify multiple cron patterns for a single job:
const clujo = new Clujo({
id: "multi-schedule-job",
cron: {
patterns: [
"0 9 * * *", // Daily at 9 AM
"0 17 * * *", // Daily at 5 PM
"0 12 * * SAT" // Saturdays at noon
]
},
runner: myRunner
});
One-time Execution
Schedule a job to run once at a specific date/time:
const clujo = new Clujo({
id: "holiday-job",
cron: {
pattern: new Date("2024-12-25T00:00:00") // Christmas midnight
},
runner: myRunner
});
Disabling a Clujo
To disable a Clujo from executing on its schedule, use the enabled
option:
const clujo = new Clujo({
id: "conditional-job",
cron: {
pattern: "*/5 * * * *"
},
runner: myRunner,
enabled: process.env.NODE_ENV === "production"
});
When disabled, the Clujo will still exist but will skip execution when triggered.
Logging
Clujo supports custom logging through a logger interface. The logger can be provided to the Clujo instance to capture various events and errors during execution. Each log level is optional, and you can choose to implement only the methods you need.
// Define a logger that implements the IClujoLogger interface
interface IClujoLogger {
debug?: (message: string) => void;
log?: (message: string) => void;
error?: (message: string) => void;
}
// Example implementation using console
const consoleLogger = {
log: (message) => console.log(`[Clujo] ${message}`),
debug: (message) => console.debug(`[Clujo] ${message}`),
error: (message) => console.error(`[Clujo] ${message}`)
};
// Or a custom logger
const customLogger = {
log: (message) => myLoggingService.info(message),
debug: (message) => myLoggingService.debug(message),
error: (message) => myLoggingService.error(message)
};
// Provide the logger to Clujo
const clujo = new Clujo({
id: "myClujoJob",
runner: myRunner,
cron: { pattern: "*/5 * * * *" },
logger: customLogger
});
The logger will capture various events such as:
- Task execution failures
- Distributed lock acquisition and release events
- Disabled execution attempts
If no logger is provided, Clujo will operate silently without logging any events.
Using Redis for Distributed Locking
When running multiple instances of your application, use Redis to ensure only one instance executes a job at a time:
import Redis from 'ioredis';
const client = new Redis();
const clujo = new Clujo({
id: "distributed-job",
cron: { pattern: "*/5 * * * *" },
runner: myRunner,
redis: {
client,
lockOptions: {
lockTimeout: 30000, // Lock expires after 30 seconds
acquireTimeout: 10000 // Wait up to 10 seconds to acquire lock
}
},
});
Running on Startup
Execute the job immediately when starting, in addition to the scheduled times:
const clujo = new Clujo({
id: "startup-job",
cron: { pattern: "0 * * * *" }, // Every hour
runner: myRunner,
runOnStartup: true // Also runs immediately when started
});
Manual Triggering
You can manually trigger a job outside of its schedule:
// This runs independently of the schedule and any running instances
const result = await clujo.trigger();
console.log("Manual execution result:", result);
Using the Scheduler
The Scheduler class provides a convenient way to manage multiple Clujo jobs together. It allows you to add, start, and stop groups of jobs in a centralized manner.
It is not required and Clujo
's can be managed manually if desired.
Adding Jobs to the Scheduler
import { Scheduler } from '@ramplex/clujo';
import { Redis } from 'ioredis';
const scheduler = new Scheduler();
// Add jobs to the scheduler
scheduler.addJob(myFirstClujo);
scheduler.addJob(mySecondClujo);
// Add more jobs as needed
Starting All Jobs
You can start all added jobs at once, optionally providing a Redis instance for distributed locking:
// Start all jobs
scheduler.start();
Stopping All Jobs
To stop all running jobs:
// Stop all jobs with a default timeout of 5000ms
await scheduler.stop();
// Or, specify a custom timeout in milliseconds
await scheduler.stop(10000);
Examples
Simple Counter
let count = 0;
const counter = {
trigger: async () => {
count++;
console.log(`Count is now: ${count}`);
return count;
}
};
const clujo = new Clujo({
id: "counter",
cron: { pattern: "*/10 * * * * *" }, // Every 10 seconds
runner: counter
});
clujo.start();
Database Cleanup
const dbCleanup = {
trigger: async () => {
const db = await getDatabase();
const deleted = await db.deleteOldRecords(30); // 30 days old
console.log(`Cleaned up ${deleted} old records`);
return { deletedCount: deleted, timestamp: new Date() };
}
};
const clujo = new Clujo({
id: "db-cleanup",
cron: { pattern: "0 2 * * *" }, // Daily at 2 AM
runner: dbCleanup,
redis: { client: redisClient }, // Prevent multiple instances from running
logger: {
log: (msg) => logger.info(msg),
error: (msg) => logger.error(msg),
debug: (msg) => logger.debug(msg)
}
});
With Workflow
Clujo works seamlessly with @ramplex/workflow
for complex task orchestration:
import { Clujo } from '@ramplex/clujo';
import { Workflow } from '@ramplex/workflow';
const workflow = new Workflow()
.addNode({
id: "fetch",
execute: async () => {
const data = await fetchData();
return data;
}
})
.addNode({
id: "process",
dependencies: ["fetch"],
execute: async (ctx) => {
const processed = await processData(ctx.fetch);
return processed;
}
})
.addNode({
id: "save",
dependencies: ["process"],
execute: async (ctx) => {
await saveResults(ctx.process);
return { saved: true };
}
})
.build();
const clujo = new Clujo({
id: "data-pipeline",
cron: { pattern: "0 */6 * * *" }, // Every 6 hours
runner: workflow,
redis: { client: redisClient }
});
clujo.start();
Contributing
Contributions are welcome! Please describe the contribution in an issue before submitting a pull request. Attach the issue number to the pull request description and include tests for new features / bug fixes.
License
@ramplex/clujo is MIT licensed.
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
8 months ago
8 months ago
4 months ago
9 months ago
10 months ago
12 months ago
5 months ago
8 months ago
8 months ago
8 months ago
12 months ago
12 months ago