| # egg-schedule | 
|   | 
| [![NPM version][npm-image]][npm-url] | 
| [![build status][travis-image]][travis-url] | 
| [![Test coverage][codecov-image]][codecov-url] | 
| [![David deps][david-image]][david-url] | 
| [![Known Vulnerabilities][snyk-image]][snyk-url] | 
| [![npm download][download-image]][download-url] | 
|   | 
| [npm-image]: https://img.shields.io/npm/v/egg-schedule.svg?style=flat-square | 
| [npm-url]: https://npmjs.org/package/egg-schedule | 
| [travis-image]: https://img.shields.io/travis/eggjs/egg-schedule.svg?style=flat-square | 
| [travis-url]: https://travis-ci.org/eggjs/egg-schedule | 
| [codecov-image]: https://codecov.io/github/eggjs/egg-schedule/coverage.svg?branch=master | 
| [codecov-url]: https://codecov.io/github/eggjs/egg-schedule?branch=master | 
| [david-image]: https://img.shields.io/david/eggjs/egg-schedule.svg?style=flat-square | 
| [david-url]: https://david-dm.org/eggjs/egg-schedule | 
| [snyk-image]: https://snyk.io/test/npm/egg-schedule/badge.svg?style=flat-square | 
| [snyk-url]: https://snyk.io/test/npm/egg-schedule | 
| [download-image]: https://img.shields.io/npm/dm/egg-schedule.svg?style=flat-square | 
| [download-url]: https://npmjs.org/package/egg-schedule | 
|   | 
| A schedule plugin for egg, has been built-in plugin for egg enabled by default. | 
|   | 
| It's fully extendable for developer and provide a simple built-in TimerStrategy. | 
|   | 
| ## Usage | 
|   | 
| Just add you job file to `{app_root}/app/schedule`. | 
|   | 
| ```js | 
| // {app_root}/app/schedule/cleandb.js | 
| const Subscription = require('egg').Subscription; | 
|   | 
| class CleanDB extends Subscription { | 
|   /** | 
|    * @property {Object} schedule | 
|    *  - {String} type - schedule type, `worker` or `all` or your custom types. | 
|    *  - {String} [cron] - cron expression, see [below](#cron-style-scheduling) | 
|    *  - {Object} [cronOptions] - cron options, see [cron-parser#options](https://github.com/harrisiirak/cron-parser#options) | 
|    *  - {String | Number} [interval] - interval expression in millisecond or express explicitly like '1h'. see [below](#interval-style-scheduling) | 
|    *  - {Boolean} [immediate] - To run a scheduler at startup | 
|    *  - {Boolean} [disable] - whether to disable a scheduler, usually use in dynamic schedule | 
|    *  - {Array} [env] - only enable scheduler when match env list | 
|    */ | 
|   static get schedule() { | 
|     return { | 
|       type: 'worker', | 
|       cron: '0 0 3 * * *', | 
|       // interval: '1h', | 
|       // immediate: true, | 
|     }; | 
|   } | 
|   | 
|   async subscribe() { | 
|     await this.ctx.service.db.cleandb(); | 
|   } | 
| } | 
|   | 
| module.exports = CleanDB; | 
| ``` | 
|   | 
| You can also use function simply. | 
|   | 
| ```js | 
| exports.schedule = { | 
|   type: 'worker', | 
|   cron: '0 0 3 * * *', | 
|   // interval: '1h', | 
|   // immediate: true, | 
| }; | 
|   | 
| exports.task = async function (ctx) { | 
|   await ctx.service.db.cleandb(); | 
| }; | 
| ``` | 
|   | 
| ## Overview | 
|   | 
| `egg-schedule` supports both cron-based scheduling and interval-based scheduling. | 
|   | 
| Schedule decision is being made by `agent` process. `agent` triggers a task and sends message to `worker` process. Then, one or all `worker` process(es) execute the task based on schedule type. | 
|   | 
| To setup a schedule task, simply create a job file in `{app_root}/app/schedule`. A file contains one job and export `schedule` and `task` properties. | 
|   | 
| The rule of thumbs is one job per file. | 
|   | 
| ## Task | 
|   | 
| Task is a class which will be instantiated every schedule, and `subscribe` method will be invoked. | 
|   | 
| You can get anonymous context with `this.ctx`. | 
|   | 
| - ctx.method: `SCHEDULE` | 
| - ctx.path: `/__schedule?path=${schedulePath}&${schedule}`. | 
|   | 
| To create a task, `subscribe` can be generator function or async function. For example: | 
|   | 
| ```js | 
| // A simple logger example | 
| const Subscription = require('egg').Subscription; | 
| class LoggerExample extends Subscription { | 
|   async subscribe() { | 
|     this.ctx.logger.info('Info about your task'); | 
|   } | 
| } | 
| ``` | 
|   | 
| ```js | 
| // A real world example: wipe out your database. | 
| // Use it with caution. :) | 
| const Subscription = require('egg').Subscription; | 
| class CleanDB extends Subscription { | 
|   async subscribe() { | 
|     await this.ctx.service.db.cleandb(); | 
|   } | 
| } | 
| ``` | 
|   | 
| ## Scheduling | 
|   | 
| `schedule` is an object that contains one required property, `type`, and optional properties, `{ cron, cronOptions, interval, immediate, disable, env }`. | 
|   | 
| ### Cron-style Scheduling | 
|   | 
| Use [cron-parser](https://github.com/harrisiirak/cron-parser). | 
|   | 
| > Note: `cron-parser` support `second` as optional that is not supported by linux crontab. | 
| > | 
| > `@hourly / @daily / @weekly / @monthly / @yearly` is also supported. | 
|   | 
| ```bash | 
| *    *    *    *    *    * | 
| ┬    ┬    ┬    ┬    ┬    ┬ | 
| │    │    │    │    │    | | 
| │    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun) | 
| │    │    │    │    └───── month (1 - 12) | 
| │    │    │    └────────── day of month (1 - 31) | 
| │    │    └─────────────── hour (0 - 23) | 
| │    └──────────────────── minute (0 - 59) | 
| └───────────────────────── second (0 - 59, optional) | 
| ``` | 
|   | 
| Example: | 
|   | 
| ```js | 
| // To execute task every 3 hours | 
| exports.schedule = { | 
|   type: 'worker', | 
|   cron: '0 0 */3 * * *', | 
|   cronOptions: { | 
|     // tz: 'Europe/Athens', | 
|   } | 
| }; | 
| ``` | 
|   | 
| ### Interval-style Scheduling | 
|   | 
| To use `setInterval`, and support [ms](https://www.npmjs.com/package/ms) conversion style | 
|   | 
| Example: | 
|   | 
| ```js | 
| // To execute task every 3 hours | 
| exports.schedule = { | 
|   type: 'worker', | 
|   interval: '3h', | 
| }; | 
| ``` | 
|   | 
| **Notice: Egg built-in TimerStrategy will schedule each execution at a fix rate, regardless of its execution time. So you have to make sure that your actual execution time of your `task/subscribe` must be smaller than your delay time.** | 
|   | 
| ### Schedule Type | 
|   | 
| **Build-in support is:** | 
|   | 
| - `worker`: will be executed in one random worker when schedule run. | 
| - `all`: will be executed in all workers when schedule run. | 
|   | 
| **Custom schedule:** | 
|   | 
| To create a custom schedule, simply extend `agent.ScheduleStrategy` and register it by `agent.schedule.use(type, clz)`. | 
| You can schedule the task to be executed by one random worker or all workers with the built-in method `this.sendOne(...args)` or `this.sendAll(...args)` which support params, it will pass to `subscribe(...args)` or `task(ctx, ...args)`. | 
|   | 
| ```js | 
| // {app_root}/agent.js | 
| module.exports = function(agent) { | 
|   class CustomStrategy extends agent.ScheduleStrategy { | 
|     start() { | 
|       // such as mq / redis subscribe | 
|       agent.notify.subscribe('remote_task', data => { | 
|         this.sendOne(data); | 
|       }); | 
|     } | 
|   } | 
|   agent.schedule.use('custsom', CustomStrategy); | 
| }; | 
| ``` | 
|   | 
| Then you could use it to defined your job: | 
|   | 
| ```js | 
| // {app_root}/app/schedule/other.js | 
| const Subscription = require('egg').Subscription; | 
| class ClusterTask extends Subscription { | 
|   static get schedule() { | 
|     return { | 
|       type: 'custom', | 
|     }; | 
|   } | 
|   async subscribe(data) { | 
|     console.log('got custom data:', data); | 
|     await this.ctx.service.someTask.run(); | 
|   } | 
| } | 
| ``` | 
|   | 
| ## Dynamic schedule | 
|   | 
| ```js | 
| // {app_root}/app/schedule/sync.js | 
| module.exports = app => { | 
|   class SyncTask extends app.Subscription { | 
|     static get schedule() { | 
|       return { | 
|         interval: 10000, | 
|         type: 'worker', | 
|         // only start task when hostname match | 
|         disable: require('os').hostname() !== app.config.sync.hostname, | 
|         // only start task at prod mode | 
|         env: [ 'prod' ], | 
|       }; | 
|     } | 
|     async subscribe() { | 
|       await this.ctx.sync(); | 
|     } | 
|   } | 
|   return SyncTask; | 
| } | 
| ``` | 
|   | 
| ## Configuration | 
|   | 
| ### Logging | 
|   | 
| See `${appInfo.root}/logs/{app_name}/egg-schedule.log` which provided by [config.customLogger.scheduleLogger](https://github.com/eggjs/egg-schedule/blob/master/config/config.default.js). | 
|   | 
| ```js | 
| // config/config.default.js | 
| config.customLogger = { | 
|   scheduleLogger: { | 
|     // consoleLevel: 'NONE', | 
|     // file: path.join(appInfo.root, 'logs', appInfo.name, 'egg-schedule.log'), | 
|   }, | 
| }; | 
| ``` | 
|   | 
| ### Customize directory | 
|   | 
| If you want to add additional schedule directories, you can use this config. | 
|   | 
| ```js | 
| // config/config.default.js | 
| config.schedule = { | 
|   directory: [ | 
|     path.join(__dirname, '../app/otherSchedule'), | 
|   ], | 
| }; | 
| ``` | 
|   | 
| ## Testing | 
|   | 
| `app.runSchedule(scheduleName)` is provided by `egg-schedule` plugin only for test purpose. | 
|   | 
| Example: | 
|   | 
| ```js | 
| it('test a schedule task', async function () { | 
|   // get app instance | 
|   await app.runSchedule('clean_cache'); | 
| }); | 
| ``` | 
|   | 
| ## Questions & Suggestions | 
|   | 
| Please open an issue [here](https://github.com/eggjs/egg/issues). | 
|   | 
| ## License | 
|   | 
| [MIT](https://github.com/eggjs/egg-schedule/blob/master/LICENSE) |