
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
@reflet/cron
Advanced tools
@reflet/cron
🌠The best decorators for node-cron. Have a look at Reflet's philosophy.
Enable them in your TypeScript compiler options.
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
Install reflect-metadata
shim.
yarn add reflect-metadata
Import the shim in your program before everything else.
import 'reflect-metadata'
Install the package along with peer dependencies.
yarn add @reflet/cron cron && yarn add -D @types/cron @types/node
Create cron jobs.
// jobs.ts
import { Cron, Expression } from '@reflet/cron'
@Cron.TimeZone('Europe/Paris')
export class Jobs {
@Cron(Expression.EVERY_SECOND)
async logMessage() {
console.log('You will see this message every second');
}
@Cron(Expression.EVERY_DAY_AT_MIDNIGHT)
async sendEmailToAdmin() {
await emailClient.send({ to: 'admin@github.com', content: 'This is fine' })
}
}
Initialize and start cron jobs.
// main.ts
import { initCronJobs } from '@reflet/cron'
import { Jobs } from './jobs.ts'
const jobs = initCronJobs(Jobs)
jobs.startAll()
🔦
@Cron(time)
🔦initCronJobs(class)
💫 Related node-cron constructor:CronJob
import { Cron, initCronJobs } from '@reflet/cron'
class Jobs {
@Cron('*/1 * * * *')
logFoo() {
console.log('foo')
}
}
const jobs = initCronJobs(Jobs) // returns a Map of your jobs, with the method names as keys.
// You can start jobs individually
jobs.get('logFoo').start()
// Or all at once
jobs.startAll()
// Same with stop
jobs.stopAll()
💡 Use Expression
enum to avoid using cron syntax:
import { Cron, Expression } from '@reflet/cron'
class Jobs {
@Cron(Expression.EVERY_MINUTE)
logFoo() {
console.log('foo')
}
}
Wrap initCronJobs
into a static method to encapsulate initialization as well:
import { initCronJobs, Cron } from '@reflet/cron'
class Jobs {
static init(...deps: ConstructorParameters<typeof Jobs>) {
return initCronJobs(new Jobs(...deps))
}
@Cron(Expression.EVERY_MINUTE)
logFoo() {
console.log('foo')
}
}
const jobs = Jobs.init()
💡 As a convenience, you can inherit from the abstract class Initializer
which already have the init
static method:
import { Initializer } from '@reflet/cron'
// You must pass the child class as type parameter to properly infer its constructor parameters.
class Jobs extends Initializer<typeof Jobs> {
@Cron(Expression.EVERY_MINUTE)
logFoo() {
console.log('foo')
}
}
const jobs = Jobs.init()
Compared to node-cron, Reflet adds 2 properties to jobs' instances:
firing
: readonly boolean
that helps us determine if the job is actually being executed. Because the original node-cron running
property defines whether the job has been started or stopped. This property is used by the PreventOverlap decorator.name
: string
in the following format: class.method
, to help distinguish jobs.🔦
@Cron.Start
,@Cron.RunOnInit
,@Cron.OnComplete(fn)
,@Cron.TimeZone(tz)
,@Cron.UtcOffset(offset)
,@Cron.UnrefTimeout
💫 Related node-cron parameters:start, runOnInit, onComplete, timezone, utcOffset, unrefTimeout
@Cron.Start
: Starts the job after init (does not immediately fire its function). Can be used with or without invokation.@Cron.RunOnInit
: Fire the job's function on init. Can be used with or without invokation.@Cron.OnComplete(fn)
: A function that will fire when the job is stopped with job.stop()
.@Cron.TimeZone(tz)
: Specify the timezone for the execution. Type is narrowed to an union of all available timezones.@Cron.UtcOffset(offset)
: Specify the offset of the timezone instead of the timezone directly. Type is narrowed to an union of all available offsets (plus number
).Please refer to the node-cron repository for more details.
import { Cron, Expression } from '@reflet/cron'
class Jobs {
@Cron.Start
@Cron.RunOnInit
@Cron.TimeZone('Europe/Paris')
@Cron(Expression.EVERY_SECOND)
doSomething() {}
@Cron.Start()
@Cron.RunOnInit()
@Cron.UtcOffset('+01:00')
@Cron.OnComplete(() => {})
@Cron(Expression.EVERY_SECOND)
doSomethingElse() {}
}
Errors happening in a cron job are automatically logged to stderr
instead of crashing the server.
🔦
@Cron.Catch(errorHandler)
This decorator allows you to do something else than logging with your errors.
import { Cron, Expression } from '@reflet/cron'
class Jobs {
@Cron.Catch(async (err) => {
console.error(err)
await db.insert(err)
})
@Cron(Expression.EVERY_SECOND)
doSomething() {}
}
🔦
@Cron.Retry(options)
If your job throw an error, you can retry it with a specify number of attempts
, and specify backoff behavior.
attempts
: Number of retry attempts.delay
: Delay between retry attemps in milliseconds.delayFactor
: Increases each time the previous delay by a multiplicative factor.delayMax
: Caps the maximum delay in milliseconds.condition
: Filter function with the error as parameter (so you can retry on specific errors only).import { Cron, Expression } from '@reflet/cron'
class Jobs {
@Cron.Retry({ attempts: 3, delay: 100, delayFactor: 2, delayMax: 1000 })
@Cron(Expression.EVERY_HOUR)
doSomething() {}
}
If all the attempts failed, then the error is logged to stderr
(or handled by Cron.Catch
).
🔦
@Cron.PreventOverlap
Prevents the job from firing if the previous occurence is not finished. Useful for potentially long jobs firing every second.
Can be used with or without invokation.
import { Cron, Expression } from '@reflet/cron'
class Jobs {
@Cron.PreventOverlap
@Cron(Expression.EVERY_SECOND)
doSomething() {}
}
🔦
@Cron.PreventOverlap.RedisLock
If your application hosting your cron jobs is running amongst multiple instances, you want to make sure a single job is firing once.
With the help of Redis and the node-redlock package, you can achieve cron locks easily.
You obviously need to install a Redis server, a nodejs Redis client, and node-redlock
.
import * as redis from 'redis'
import * as Redlock from 'redlock'
import { Cron, Expression } from '@reflet/cron'
const redisClient = redis.createClient()
@Cron.PreventOverlap.RedisLock((job) => {
const redlock = new Redlock([redisClient], { retryCount: 0 })
return redlock.lock(`lock:${job.name}`, 1000)
// use each job's name as a unique resource ("Jobs.doSomething" in this case).
})
class Jobs {
@Cron(Expression.EVERY_MINUTE)
doSomething() {}
}
Reflet will handle the unlocking, once the job is over.
If @Cron.Retry
is also applied, the lock will be reacquired on retries with the original ttl
and the eventual retry delay.
🗣️ By default, redlock retryCount
is set to 10
, the documentation recommends to set retryCount
to 0
for most tasks (especially for high frequency jobs).
In that regard, if you want to have a look at failed locks, you can simply .catch
your lock promise:
return redlock.lock(`lock:${job.name}`, 1000).catch(console.warn)
Go to node-redlock documentation for more details.
🔦
@Cron.Options(allOptions)
If you prefer, you can use @Cron.Options
to group all your options in a single decorator:
import { Cron } from '@reflet/cron'
class Jobs {
@Cron.Options({
start: true,
runOnInit: true,
retry: { attempts: 3, delay: 200 },
catchError: async (err) => {
await db.insert(err)
}
})
@Cron('* * * * *')
doSomething() {}
}
Attach any option decorator to the class to share it with all jobs. You can override it by reattaching it to the method.
Boolean option decorators (@Cron.Start
, @Cron.RunOnInit
, @Cron.UnrefTimeout
, @Cron.PreventOverlap
) can be turned to false with their special sub decorator: @Cron.XXX.Dont
(can be used with or without invokation)
@Cron.Start
@Cron.PreventOverlap
@Cron.Retry({ maxRetries: 3, delay: 200 })
class Jobs {
@Cron(Expression.EVERY_SECOND)
doSomething() {}
@Cron.Start.Dont
@Cron(Expression.EVERY_SECOND)
doSomethingElse() {}
}
🔦
@CurrentJob
You might need to access the current job instance from its own onTick
function, for example to simply stop it.
Can be used with or without invokation.
import { Cron, Expression, CurrentJob } from '@reflet/cron'
class Jobs {
@Cron(Expression.EVERY_SECOND)
doSomething(@CurrentJob job: Job) {
job.stop()
}
}
You can add dynamic cron jobs like so:
import { Cron, Expression, initCronJobs } from '@reflet/cron'
@Cron.Start
class Jobs {
@Cron(Expression.EVERY_SECOND)
doSomething() {}
}
const jobs = initCronJobs(Jobs)
jobs.set('doSomethingElse', {
cronTime: Expression.EVERY_HOUR,
onTick() {
console.log("I'm a dynamic job")
},
runOnInit: true,
preventOverlap: true
})
Dynamic jobs inherit shared class options.
If you want to go full OOP and your job classes has constructor dependencies, Reflet will enforce passing them as instances (along with their dependencies) instead of classes.
import { Cron, Expression, initCronJobs } from '@reflet/cron'
class Service {
user = 'Jeremy'
}
class Jobs {
static init(...deps: ConstructorParameters<typeof Jobs>) {
return initCronJobs(new Jobs(...deps))
}
constructor(private service: Service) {}
@Cron(Expression.EVERY_10_MINUTES)
doSomething() {
console.log(this.service.user)
}
}
const jobs = Jobs.init(new Service())
FAQs
Well-defined and well-typed cron decorators
The npm package @reflet/cron receives a total of 6 weekly downloads. As such, @reflet/cron popularity was classified as not popular.
We found that @reflet/cron demonstrated a not healthy version release cadence and project activity because the last version was released 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.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.