Gathering detailed insights and metrics for toad-scheduler
Gathering detailed insights and metrics for toad-scheduler
Gathering detailed insights and metrics for toad-scheduler
Gathering detailed insights and metrics for toad-scheduler
npm install toad-scheduler
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
568 Stars
114 Commits
25 Forks
7 Watching
3 Branches
7 Contributors
Updated on 25 Nov 2024
TypeScript (99.18%)
JavaScript (0.82%)
Cumulative downloads
Total Downloads
Last day
-5.7%
7,510
Compared to previous day
Last week
19.4%
41,606
Compared to previous week
Last month
-8.3%
178,915
Compared to previous month
Last year
98%
2,168,180
Compared to previous year
In-memory TypeScript job scheduler that repeatedly executes given tasks within specified intervals of time (e. g. "each 20 seconds"). Cron syntax is also supported in case you need it.
Node.js 12+ and modern browsers are supported
First install the package:
1npm i toad-scheduler
Next, set up your jobs:
1const { ToadScheduler, SimpleIntervalJob, Task } = require('toad-scheduler') 2 3const scheduler = new ToadScheduler() 4 5const task = new Task('simple task', () => { counter++ }) 6const job = new SimpleIntervalJob({ seconds: 20, }, task) 7 8scheduler.addSimpleIntervalJob(job) 9 10// when stopping your app 11scheduler.stop()
In order to avoid unhandled rejections, make sure to use AsyncTask if your task is asynchronous:
1const { ToadScheduler, SimpleIntervalJob, AsyncTask } = require('toad-scheduler') 2 3const scheduler = new ToadScheduler() 4 5const task = new AsyncTask( 6 'simple task', 7 () => { return db.pollForSomeData().then((result) => { /* continue the promise chain */ }) }, 8 (err: Error) => { /* handle error here */ } 9) 10const job = new SimpleIntervalJob({ seconds: 20, }, task) 11 12scheduler.addSimpleIntervalJob(job) 13 14// when stopping your app 15scheduler.stop()
Note that in order to avoid memory leaks, it is recommended to use promise chains instead of async/await inside task definition. See talk on common Promise mistakes for more details.
Note that your error handlers can be asynchronous and return a promise. In such case an additional catch block will be attached to them, and should
there be an error while trying to resolve that promise, and logging error will be logged using the default error handler (console.error
).
In case you want to prevent second instance of a task from being fired up while first one is still executing, you can use preventOverrun
options:
1import { ToadScheduler, SimpleIntervalJob, Task } from 'toad-scheduler';
2
3const scheduler = new ToadScheduler();
4
5const task = new Task('simple task', () => {
6 // if this task runs long, second one won't be started until this one concludes
7 console.log('Task triggered');
8});
9
10const job = new SimpleIntervalJob(
11 { seconds: 20, runImmediately: true },
12 task,
13 {
14 id: 'id_1',
15 preventOverrun: true,
16 }
17);
18
19//create and start jobs
20scheduler.addSimpleIntervalJob(job);
You can attach IDs to tasks to identify them later. This is helpful in projects that run a lot of tasks and especially if you want to target some of the tasks specifically (e. g. in order to stop or restart them, or to check their status).
1import { ToadScheduler, SimpleIntervalJob, Task } from 'toad-scheduler';
2
3const scheduler = new ToadScheduler();
4
5const task = new Task('simple task', () => {
6 console.log('Task triggered');
7});
8
9const job1 = new SimpleIntervalJob(
10 { seconds: 20, runImmediately: true },
11 task,
12 { id: 'id_1' }
13);
14
15const job2 = new SimpleIntervalJob(
16 { seconds: 15, runImmediately: true },
17 task,
18 { id: 'id_2' }
19);
20
21//create and start jobs
22scheduler.addSimpleIntervalJob(job1);
23scheduler.addSimpleIntervalJob(job2);
24
25// stop job with ID: id_2
26scheduler.stopById('id_2');
27
28// remove job with ID: id_1
29scheduler.removeById('id_1');
30
31// check status of jobs
32console.log(scheduler.getById('id_1').getStatus()); // returns Error (job not found)
33
34console.log(scheduler.getById('id_2').getStatus()); // returns "stopped" and can be started again
35
You can use CronJob instances for handling Cron-style scheduling:
1 const task = new AsyncTask('simple task', () => {
2 // Execute your asynchronous logic here
3 })
4 const job = new CronJob(
5 {
6 cronExpression: '*/2 * * * * *',
7 },
8 task,
9 {
10 preventOverrun: true,
11 }
12 )
13 scheduler.addCronJob(job)
Note that you need to install "croner" library for this to work. Run npm i croner
in order to install this dependency.
toad-scheduler
does not persist its state by design, and has no out-of-the-box concurrency management features. In case it is necessary
to prevent parallel execution of jobs in clustered environment, it is highly recommended to use redis-semaphore in your tasks.
Here is an example:
1import { randomUUID } from 'node:crypto' 2 3import type { Redis } from 'ioredis' 4import type { LockOptions } from 'redis-semaphore' 5import { Mutex } from 'redis-semaphore' 6import { AsyncTask } from 'toad-scheduler'; 7 8export type BackgroundJobConfiguration = { 9 jobId: string 10} 11 12export type LockConfiguration = { 13 lockName?: string 14 refreshInterval?: number 15 lockTimeout: number 16} 17 18// Abstract Job 19export abstract class AbstractBackgroundJob { 20 public readonly jobId: string 21 protected readonly redis: Redis 22 23 protected constructor( 24 options: BackgroundJobConfiguration, 25 redis: Redis, 26 ) { 27 this.jobId = options.jobId 28 this.redis = redis 29 } 30 31 protected abstract processInternal(executionUuid: string): Promise<void> 32 33 async process() { 34 const uuid = randomUUID() 35 36 try { 37 await this.processInternal(uuid) 38 } catch (err) { 39 console.error(logObject) 40 } 41 } 42 43 protected getJobMutex(key: string, options: LockOptions) { 44 return new Mutex(this.redis, this.getJobLockName(key), options) 45 } 46 47 protected async tryAcquireExclusiveLock(lockConfiguration: LockConfiguration) { 48 const mutex = this.getJobMutex(lockConfiguration.lockName ?? 'exclusive', { 49 acquireAttemptsLimit: 1, 50 refreshInterval: lockConfiguration.refreshInterval, 51 lockTimeout: lockConfiguration.lockTimeout, 52 }) 53 54 const lock = await mutex.tryAcquire() 55 // If someone else already has this lock, skip 56 if (!lock) { 57 return 58 } 59 60 return mutex 61 } 62 63 protected getJobLockName(key: string) { 64 return `${this.jobId}:locks:${key}` 65 } 66} 67 68// Job example 69 70const LOCK_TIMEOUT_IN_MSECS = 60 * 1000 71const LOCK_REFRESH_IN_MSECS = 10 * 1000 72 73export class SampleJob extends AbstractBackgroundJob { 74 constructor(redis: Redis) { 75 super( 76 { 77 jobId: 'SampleJob', 78 }, 79 redis, 80 ) 81 } 82 83 protected async processInternal(executionUuid: string): Promise<void> { 84 // We only want a single instance of this job running in entire cluster, let's see if someone else is already processing it 85 const lock = await this.tryAcquireExclusiveLock({ 86 lockTimeout: LOCK_TIMEOUT_IN_MSECS, 87 refreshInterval: LOCK_REFRESH_IN_MSECS, 88 }) 89 90 // Job is already running, skip 91 if (!lock) { 92 this.logger.debug(`Job already running in another node, skipping (${executionUuid})`) 93 return 94 } 95 96 try { 97 // Process job logic here 98 await this.sampleJobLogic() 99 } finally { 100 await lock.release() 101 } 102 } 103 104 private async sampleJobLogic() { 105 // dummy processing logic 106 return Promise.resolve() 107 } 108 109 110// Job registration 111function createTask(job: AbstractBackgroundJob): AsyncTask { 112 return new AsyncTask( 113 job.jobId, 114 () => { 115 return job.process() 116 }, 117 ) 118}
days?: number
- how many days to wait before executing the job for the next time;hours?: number
- how many hours to wait before executing the job for the next time;minutes?: number
- how many minutes to wait before executing the job for the next time;seconds?: number
- how many seconds to wait before executing the job for the next time;milliseconds?: number
- how many milliseconds to wait before executing the job for the next time;runImmediately?: boolean
- if set to true, in addition to being executed on a given interval, job will also be executed immediately when added or restarted.start(): void
- starts, or restarts (if it's already running) the job;stop(): void
- stops the job. Can be restarted again with start
command;getStatus(): JobStatus
- returns the status of the job, which is one of: running
, stopped
.addSimpleIntervalJob(job: SimpleIntervalJob): void
- registers and starts a new job;addLongIntervalJob(job: LongIntervalJob): void
- registers and starts a new job with support for intervals longer than 24.85 days;addIntervalJob(job: SimpleIntervalJob | LongIntervalJob): void
- registers and starts new interval-based job;stop(): void
- stops all jobs, registered in the scheduler;getById(id: string): Job
- returns the job with a given id.existsById(id: string): boolean
- returns true if job with given id exists, false otherwise.getAllJobs(): Job[]
- returns all registered jobs (only jobs with an id are in the registry).getAllJobsByStatus(status: JobStatus): Job[]
- returns all registered jobs with a given status.stopById(id: string): void
- stops the job with a given id.removeById(id: string): Job | undefined
- stops the job with a given id and removes it from the scheduler. If no such job exists, returns undefined
, otherwise returns the job.startById(id: string): void
- starts, or restarts (if it's already running) the job with a given id.No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
0 existing vulnerabilities detected
Reason
Found 4/20 approved changesets -- score normalized to 2
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
Reason
security policy file not detected
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More