
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
@fluojs/cron
Advanced tools
Decorator-based task scheduling for Fluo with cron, interval, and timeout triggers and optional distributed locking.
English 한국어
Decorator-based scheduling for fluo applications with lifecycle-managed startup/shutdown and optional Redis distributed locking.
npm install @fluojs/cron croner
croner is the scheduler engine used by @fluojs/cron. Install it alongside the package so lockfiles make the runtime scheduler dependency explicit for applications and deployment audits.
@fluojs/redis is needed only when Redis distributed locking is enabled. Non-distributed scheduling paths do not load the Redis integration during package import, module registration, bootstrap, or status snapshot creation.
Register the CronModule and use decorators to schedule your methods.
Use CronModule.forRoot(...) to register scheduling for an application module.
Cron expressions may use either five fields (minute hour day month weekday) or six fields (second minute hour day month weekday). The built-in CronExpression presets use six-field expressions when sub-minute precision is needed. Cron tasks start only after application bootstrap, dynamically registered cron tasks start when added to a started registry, and fluo forwards timezone plus no-overlap protection to the scheduler so one task instance does not overlap itself.
Scheduling decorators apply to public instance methods only. Do not migrate NestJS private scheduled methods, static helpers, or method names that are hidden behind legacy decorator metadata assumptions as-is; expose a public provider/controller method and keep any private implementation details behind that method.
import { Module } from '@fluojs/core';
import { CronModule, Cron, CronExpression, Interval, Timeout } from '@fluojs/cron';
class BillingService {
@Cron(CronExpression.EVERY_MINUTE, { name: 'billing.reconcile' })
async reconcilePendingInvoices() {
console.log('Reconciling invoices...');
}
@Interval(15_000) // 15 seconds
async pollStatus() {
console.log('Polling status...');
}
@Timeout(5_000) // 5 seconds after startup
async initialSync() {
console.log('Running initial sync...');
}
}
@Module({
imports: [CronModule.forRoot()],
providers: [BillingService],
})
class AppModule {}
To prevent scheduled tasks from running concurrently across multiple server instances, enable distributed mode. This requires @fluojs/redis; the Redis peer is loaded and resolved only when distributed.enabled is true.
import { Module } from '@fluojs/core';
import { CronModule } from '@fluojs/cron';
import { RedisModule } from '@fluojs/redis';
@Module({
imports: [
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
CronModule.forRoot({
distributed: {
enabled: true,
keyPrefix: 'fluo:cron:lock',
lockTtlMs: 30_000,
},
}),
],
})
class AppModule {}
Leave distributed.clientName unset to keep using the default Redis registration above. To use a non-default Redis connection for distributed locks, set distributed.clientName to the name registered through RedisModule.forRoot({ name, ... }).
distributed.lockTtlMs must stay at or above 1_000ms. fluo renews the Redis lock before that TTL expires, including the minimum supported 1_000ms boundary.
Each scheduler instance uses a platform-neutral default distributed.ownerId; set distributed.ownerId explicitly only when your deployment has a stronger stable-owner convention. Lock release runs in a finally path after task execution. If Redis release fails, fluo keeps local ownership in status snapshots and retries during shutdown; if Redis reports that another owner holds the key, local ownership is cleared because fencing has already moved elsewhere. Redis TTL and renewal timing are still drift-sensitive coordination primitives rather than hard fencing tokens, so long-running jobs should remain idempotent and use application-level fencing when stale work would be unsafe.
@Module({
imports: [
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
RedisModule.forRoot({ name: 'locks', host: 'localhost', port: 6380 }),
CronModule.forRoot({
distributed: {
clientName: 'locks',
enabled: true,
keyPrefix: 'fluo:cron:lock',
lockTtlMs: 30_000,
},
}),
],
})
class MultiRedisCronModule {}
You can manage tasks at runtime using the SCHEDULING_REGISTRY.
import { Inject } from '@fluojs/core';
import { SCHEDULING_REGISTRY, type SchedulingRegistry } from '@fluojs/cron';
@Inject(SCHEDULING_REGISTRY)
class TaskManager {
constructor(private readonly registry: SchedulingRegistry) {}
addNewTask() {
this.registry.addCron('dynamic-job', '0 * * * *', () => {
console.log('Dynamic job running!');
});
}
speedUpPolling() {
this.registry.updateIntervalMs('inventory.poll', 5_000);
}
stopTask() {
this.registry.remove('dynamic-job');
}
}
The registry exposes addCron, addInterval, addTimeout, remove, enable, disable, get, getAll, updateCronExpression, and updateIntervalMs. The first name argument is the default registry key; passing options.name overrides the actual registry key, scheduler metadata name, and default distributed lock key for dynamic tasks so dynamic registration matches decorator naming semantics. get and getAll return read-only SchedulingTaskDescriptor values, not live CronJob handles. Timeout tasks run once, then disable themselves while remaining in the registry so they can be re-enabled deliberately.
Dynamic cron registration is atomic with scheduler startup: if the scheduler rejects a new cron job, the registry does not retain a half-registered task. Updating a running cron expression or interval cadence is also rollback-safe. If rescheduling fails, the previous expression or interval milliseconds and scheduled handle remain active. Cron tasks use both scheduler-level no-overlap protection and fluo's in-process running guard, so the same task instance will not run overlapping ticks.
CronModule drains active task executions during application shutdown with a bounded timeout so one hung task cannot block process termination forever.
By default the shutdown drain waits up to 10_000ms. If that timeout expires, the scheduler logs a warning and continues shutdown without waiting for the hung task to settle. When distributed locking is enabled, locks held by still-running tasks are not eagerly released on timeout; they remain owned by that task until it settles normally, or until Redis expires the lock after the process exits. This prevents another node from starting the same job while the original task is still running.
@Module({
imports: [
CronModule.forRoot({
shutdown: {
timeoutMs: 5_000,
},
}),
],
})
class AppModule {}
Only singleton providers/controllers are scheduled. Request-scoped and transient scheduled classes are skipped with a warning.
CronModule.forRoot(options): Configures the scheduler and enables distributed locking if requested.@Cron(expression, options?): Schedules a method using a cron expression.@Interval(ms, options?): Schedules a method to run at a fixed interval.@Timeout(ms, options?): Schedules a method to run once after a delay.CronExpression: Enum-like object with common cron patterns, including sub-minute presets such as EVERY_SECOND, EVERY_5_SECONDS, and EVERY_30_SECONDS.SCHEDULING_REGISTRY: Injection token for the SchedulingRegistry service.normalizeCronModuleOptions(...): Normalizes module options and defaults.createCronPlatformStatusSnapshot(...): Creates a status snapshot for health/readiness integrations.SchedulingTaskKind, SchedulingTaskCallback, SchedulingTaskOptions, CronTaskOptions, IntervalTaskOptions, TimeoutTaskOptions, CronTaskMetadata, IntervalTaskMetadata, TimeoutTaskMetadata, SchedulingTaskMetadata, CronTaskDescriptor, SchedulingTaskDescriptor, and SchedulingRegistry.CronModuleOptions, NormalizedCronModuleOptions, CronDistributedOptions, CronShutdownOptions, CronScheduleOptions, CronScheduler, and CronScheduledJob.CronLifecycleState, CronStatusAdapterInput, and CronPlatformStatusSnapshot.defineSchedulingTaskMetadata, defineCronTaskMetadata, getSchedulingTaskMetadata, getCronTaskMetadata, getSchedulingTaskMetadataEntries, getCronTaskMetadataEntries, schedulingMetadataSymbol, cronMetadataSymbol.@fluojs/redis: Required for distributed locking functionality.@fluojs/core: Required for DI and Module management.croner: The underlying scheduling engine.packages/cron/src/module.test.ts: Comprehensive tests for decorators and module lifecycle.packages/cron/src/service.ts: Runtime scheduling, registry, and shutdown behavior.packages/cron/src/status.test.ts: Status snapshot behavior.packages/cron/src/distributed-lock-manager.ts: Redis distributed lock behavior.FAQs
Decorator-based task scheduling for Fluo with cron, interval, and timeout triggers and optional distributed locking.
We found that @fluojs/cron demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.