├── .gitignore ├── deno-cron-hd.png ├── .prettierrc ├── .editorconfig ├── LICENSE.md ├── test.ts ├── README.md └── cron.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode -------------------------------------------------------------------------------- /deno-cron-hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbrahul/deno_cron/HEAD/deno-cron-hd.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "semi": true, 5 | "trailingComma": "all", 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Rahul Baruri 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import { validate } from './cron.ts'; 2 | 3 | const assert = (value1: any, value2: any) => { 4 | if (value1 !== value2) { 5 | console.error( 6 | '❌ Failed:\n + Expected: ' + 7 | String(value2) + 8 | '\n - Received: ' + 9 | String(value1), 10 | ); 11 | } else { 12 | console.log('✅ success:'); 13 | } 14 | }; 15 | 16 | const test = (() => { 17 | let counter = 0; 18 | return (description: string) => (params: string, date: Date) => { 19 | console.log(`${++counter} - ${description}`); 20 | assert(validate(params, date).didMatch, true); 21 | }; 22 | })(); 23 | 24 | test( 25 | "Should execute on 1st November Monday at 10 O'clock, 5th minute and 6th second with range 1-6", 26 | )('1-8 05 10 01 11 0', new Date('2020-11-01 10:05:06')); 27 | 28 | test( 29 | "Should execute on 1st November Monday at 10 O'clock, 5th minute and 6th second with comma separated minute 1,3,5", 30 | )('06 1,3,5 10 01 11 0', new Date('2020-11-01 10:05:06')); 31 | 32 | test( 33 | "Should execute on 4th February Thursday at 10 O'clock, 7th minute and 6th second with comma separated minute 1,3,7 and */5 occurance", 34 | )('6 1,3,7 */5 04 1-4 0,4', new Date('2021-02-04 10:07:06')); 35 | 36 | test('Should execute on every week at monday mid night')( 37 | '1 0 0 * * 1', 38 | new Date('2021-03-08 00:00:01'), 39 | ); 40 | 41 | test('Should execute on every week at mid night')( 42 | '1 0 0 * * 1', 43 | new Date('2021-01-04 00:00:01'), 44 | ); 45 | 46 | test('Should execute on every year january 1 at midnight ')( 47 | '1 0 0 * 1 *', 48 | new Date('2021-01-01 00:00:01'), 49 | ); 50 | 51 | test('Should execute on every month at midnight ')( 52 | '1 0 0 1 */1 *', 53 | new Date('2021-01-01 00:00:01'), 54 | ); 55 | 56 | test('Should execute on bi-weekly at mid night')( 57 | '1 0 0 */14 * *', 58 | new Date('2021-01-28 00:00:01'), 59 | ); 60 | 61 | test('Should execute on daily at mid night')( 62 | '0 0 * * *', 63 | new Date('2021-01-27 00:00:01'), 64 | ); 65 | 66 | test('Should execute hourly at 1st second')( 67 | '1 0 * * * *', 68 | new Date('2021-01-27 01:00:01'), 69 | ); 70 | 71 | test('Should execute every minute at 1st second')( 72 | '1 * * * * *', 73 | new Date('2021-01-27 01:01:01'), 74 | ); 75 | 76 | test('Should execute every 15 minute at 1st second')( 77 | '1 */15 * * * *', 78 | new Date('2021-01-27 03:45:01'), 79 | ); 80 | 81 | test('Should execute yearly 1st of January at mid-night')( 82 | '1 0 0 1 1 *', 83 | new Date('2021-01-01 00:00:01'), 84 | ); 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno_cron 2 | 3 | A smart cron Job scheduler library for Deno. It allows you to write human readable cron syntax with tons of flexibility. Writing cron syntax and operation can be very tadious for many developers. This extensions provides very developer friendly api to write any job scheduler's cron syntax you need. 4 | 5 | ![Deno Cron](https://raw.githubusercontent.com/rbrahul/deno_cron/master/deno-cron-hd.png) 6 | 7 | 8 | ## Installation: 9 | 10 | ```javascript 11 | import {cron, daily, monthly, weekly} from 'https://deno.land/x/deno_cron/cron.ts'; 12 | 13 | daily(() => { 14 | backupDatabase(); 15 | }); 16 | 17 | 18 | weekly(() => { 19 | sendNewsLetter(); 20 | }); 21 | 22 | // Runs the Job on 5th day of every month 23 | monthly(() => { 24 | sendUsageReport(); 25 | }, 5); 26 | 27 | // Run Job in every 30 minutes 28 | cron('1 */30 * * * *', () => { 29 | checkStock(); 30 | }); 31 | 32 | ``` 33 | 34 | ## Writing CRON Job: 35 | 36 | Here is the CRON style syntax to write tons of custom cron schedule. 37 | 38 | ``` 39 | * * * * * * 40 | ┬ ┬ ┬ ┬ ┬ ┬ 41 | │ │ │ │ │ │ 42 | │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sunday) 43 | │ │ │ │ └───── month (1 - 12) 44 | │ │ │ └────────── day of month (1 - 31) 45 | │ │ └─────────────── hour (0 - 23) 46 | │ └──────────────────── minute (0 - 59) 47 | └───────────────────────── second (0 - 59) - [Optional 01 as default] 48 | ``` 49 | 50 | ### Syntax Table: 51 | 52 | | Field | Required | Allowed Values | Allowed Special Character | 53 | | ------------- |:-------------:|:----------------------:|:------------------:| 54 | | Seconds | No | 0-59 | `/` `-` `,` `*` | 55 | | Minute | Yes | 0-59 | `/` `-` `,` `*` | 56 | | Hour | Yes | 0-23 | `/` `-` `,` `*` | 57 | | Day of Month | Yes | 1-31 | `/` `-` `,` `*` | 58 | | Month | Yes | 1-12 | `/` `-` `,` `*` | 59 | | Day of Week | Yes | 0-6 (0 is Sunday) | `/` `-` `,` `*` | 60 | 61 | #### Example: 62 | 63 | ```javascript 64 | // This Job will be executed 1st day of every month at mid-night. 65 | cron('1 0 0 1 */1 *', () => { 66 | sendMails(); 67 | }); 68 | 69 | // Job will be executed for 1st to 7th day of every month on 3rd, 6th and 9th hour and every 30 minutes if it's monday 70 | 71 | cron('1 */30 3,6,9 1-7 */1 1', () => { 72 | sendMails(); 73 | }); 74 | 75 | ``` 76 | 77 | ## API: 78 | 79 | | Function | Parameter | 80 | | ---------------------------- |:--------------------------------------------| 81 | | `cron(schedule, job)` | **schedule: string** `required` - cron syntax, **job: func** `required` - function to be executed| 82 | | `everyMinute(job)` | **job: func** `required` - function to be executed | 83 | | `every15Minute(job)` | **job: func** `required` - function to be executed | 84 | | `hourly(job)` | **job: func** `required` - function to be executed | 85 | | `daily(job)` | **job: func** `required` - function to be executed | 86 | | `weekly(job, weekDay?)` | **job: func** `required` - function to be executed, **weekDay: string** or **number** **{optional}** - Represents weekday; 0-6, (0 represents Sunday) `default:` **1** | 87 | | `biweekly(job)` | **job: func** `required` - function to be executed | 88 | | `monthly(job, dayOfMonth)` | **job: func** `required` - function to be executed, **dayOfMonth: string** or **number** **{optional}** - 1-31, `default:` **1** | 89 | | `yearly(job)` | **job: func** **required** - function to be executed | 90 | | `stop()` | - Stop all the scheduled job | 91 | | `start()` | - If schedulers have been stopped before then starts again | 92 | 93 | 94 | ## Change Log: 95 | * Initial version released on - 23-05-2020 96 | 97 | ## Contributors: 98 | 99 | [Rahul Baruri](https://www.linkedin.com/in/rahul-baruri-23312311a/) 100 | 101 | ## License 102 | Distributed under the [MIT License](https://github.com/rbrahul/deno_cron/blob/master/LICENSE.md). 103 | 104 | [https://github.com/rbrahul/deno_cron/blob/master/README.md](https://github.com/rbrahul/deno_cron/blob/master/README.md) 105 | 106 | 107 | **Developed with ❤️ for Deno community** 108 | -------------------------------------------------------------------------------- /cron.ts: -------------------------------------------------------------------------------- 1 | type JobType = () => void; 2 | 3 | enum TIME_PART { 4 | SECOND = 'SECOND', 5 | MINUTE = 'MINUTE', 6 | HOUR = 'HOUR', 7 | DAY_OF_WEEK = 'DAY_OF_WEEK', 8 | DAY_OF_MONTH = 'DAY_OF_MONTH', 9 | MONTH = 'MONTH', 10 | } 11 | 12 | const schedules = new Map>(); 13 | 14 | let schedulerTimeIntervalID: ReturnType = 0; 15 | let shouldStopRunningScheduler = false; 16 | 17 | export const cron = (schedule: string = '', job: JobType) => { 18 | let jobs = schedules.has(schedule) 19 | ? [...(schedules.get(schedule) || []), job] 20 | : [job]; 21 | schedules.set(schedule, jobs); 22 | }; 23 | 24 | const isRange = (text: string) => /^\d\d?\-\d\d?$/.test(text); 25 | 26 | const getRange = (min: number, max: number) => { 27 | const numRange = []; 28 | let lowerBound = min; 29 | while (lowerBound <= max) { 30 | numRange.push(lowerBound); 31 | lowerBound += 1; 32 | } 33 | return numRange; 34 | }; 35 | 36 | const { DAY_OF_MONTH, DAY_OF_WEEK, HOUR, MINUTE, MONTH, SECOND } = TIME_PART; 37 | 38 | const getTimePart = (date: Date, type: TIME_PART): number => 39 | ({ 40 | [SECOND]: date.getSeconds(), 41 | [MINUTE]: date.getMinutes(), 42 | [HOUR]: date.getHours(), 43 | [MONTH]: date.getMonth() + 1, 44 | [DAY_OF_WEEK]: date.getDay(), 45 | [DAY_OF_MONTH]: date.getDate(), 46 | }[type]); 47 | 48 | const isMatched = (date: Date, timeFlag: string, type: TIME_PART): boolean => { 49 | const timePart = getTimePart(date, type); 50 | 51 | if (timeFlag === '*') { 52 | return true; 53 | } else if (Number(timeFlag) === timePart) { 54 | return true; 55 | } else if (timeFlag.includes('/')) { 56 | const [_, executeAt = '1'] = timeFlag.split('/'); 57 | return timePart % Number(executeAt) === 0; 58 | } else if (timeFlag.includes(',')) { 59 | const list = timeFlag.split(',').map((num: string) => parseInt(num)); 60 | return list.includes(timePart); 61 | } else if (isRange(timeFlag)) { 62 | const [start, end] = timeFlag.split('-'); 63 | const list = getRange(parseInt(start), parseInt(end)); 64 | return list.includes(timePart); 65 | } 66 | return false; 67 | }; 68 | 69 | export const validate = (schedule: string, date: Date = new Date()) => { 70 | // @ts-ignore 71 | const timeObj: Record = {}; 72 | 73 | const [ 74 | dayOfWeek, 75 | month, 76 | dayOfMonth, 77 | hour, 78 | minute, 79 | second = '01', 80 | ] = schedule.split(' ').reverse(); 81 | 82 | const cronValues = { 83 | [SECOND]: second, 84 | [MINUTE]: minute, 85 | [HOUR]: hour, 86 | [MONTH]: month, 87 | [DAY_OF_WEEK]: dayOfWeek, 88 | [DAY_OF_MONTH]: dayOfMonth, 89 | }; 90 | 91 | for (const key in cronValues) { 92 | timeObj[key as TIME_PART] = isMatched( 93 | date, 94 | cronValues[key as TIME_PART], 95 | key as TIME_PART, 96 | ); 97 | } 98 | 99 | const didMatch = Object.values(timeObj).every(Boolean); 100 | return { 101 | didMatch, 102 | entries: timeObj, 103 | }; 104 | }; 105 | 106 | const executeJobs = () => { 107 | const date = new Date(); 108 | schedules.forEach((jobs, schedule) => { 109 | if (validate(schedule, date).didMatch) { 110 | jobs.forEach((job) => job()); 111 | } 112 | }); 113 | }; 114 | 115 | const runScheduler = () => { 116 | schedulerTimeIntervalID = setInterval(() => { 117 | if (shouldStopRunningScheduler) { 118 | clearInterval(schedulerTimeIntervalID); 119 | return; 120 | } 121 | executeJobs(); 122 | }, 1000); 123 | }; 124 | 125 | export const everyMinute = (cb: JobType) => { 126 | cron(`1 * * * * *`, cb); 127 | }; 128 | 129 | export const every15Minute = (cb: JobType) => { 130 | cron(`1 */15 * * * *`, cb); 131 | }; 132 | 133 | export const hourly = (cb: JobType) => { 134 | cron(`1 0 * * * *`, cb); 135 | }; 136 | 137 | export const daily = (cb: JobType) => { 138 | cron(`1 0 0 * * *`, cb); 139 | }; 140 | 141 | export const weekly = (cb: JobType, weekDay: string | number = 1) => { 142 | cron(`1 0 0 * * ${weekDay}`, cb); 143 | }; 144 | 145 | export const biweekly = (cb: JobType) => { 146 | cron(`1 0 0 */14 * *`, cb); 147 | }; 148 | 149 | export const monthly = (cb: JobType, dayOfMonth: string | number = 1) => { 150 | cron(`1 0 0 ${dayOfMonth} */1 *`, cb); 151 | }; 152 | 153 | export const yearly = (cb: JobType) => { 154 | cron(`1 0 0 1 1 *`, cb); 155 | }; 156 | 157 | export const start = () => { 158 | if (shouldStopRunningScheduler) { 159 | shouldStopRunningScheduler = false; 160 | runScheduler(); 161 | } 162 | }; 163 | 164 | export const stop = () => { 165 | shouldStopRunningScheduler = true; 166 | }; 167 | 168 | runScheduler(); 169 | --------------------------------------------------------------------------------