Bree
Bree is a Node.js and JavaScript job task scheduler with worker threads, cr...
README
Table of Contents
Foreword
Install
- ```sh
- npm install bree
- ```
- ```sh
- yarn add bree
- ```
Upgrading
IMPORTANT: Bree v9.0.0 has several breaking changes, please see UPGRADING.md for more insight.
NOTE: Bree v6.5.0 is the last version to support Node v10 and browsers.
Usage and Examples
ECMAScript modules (ESM)
- ``` js
- // app.mjs
- import Bree from 'bree';
- const bree = new Bree({
- // ... (see below) ...
- });
- // top-level await supported in Node v14.8+
- await bree.start();
- // ... (see below) ...
- ```
CommonJS (CJS)
- ``` js
- // app.js
- const path = require('path');
- // optional
- const ms = require('ms');
- const dayjs = require('dayjs');
- const Graceful = require('@ladjs/graceful');
- const Cabin = require('cabin');
- // required
- const Bree = require('bree');
- //
- // NOTE: see the "Instance Options" section below in this README
- // for the complete list of options and their defaults
- //
- const bree = new Bree({
- //
- // NOTE: by default the `logger` is set to `console`
- // however we recommend you to use CabinJS as it
- // will automatically add application and worker metadata
- // to your log output, and also masks sensitive data for you
- //
- //
- // NOTE: You can also pass `false` as `logger: false` to disable logging
- //
- logger: new Cabin(),
- //
- // NOTE: instead of passing this Array as an option
- // you can create a `./jobs/index.js` file, exporting
- // this exact same array as `module.exports = [ ... ]`
- // doing so will allow you to keep your job configuration and the jobs
- // themselves all in the same folder and very organized
- //
- // See the "Job Options" section below in this README
- // for the complete list of job options and configurations
- //
- jobs: [
- // runs `./jobs/foo.js` on start
- 'foo',
- // runs `./jobs/foo-bar.js` on start
- {
- name: 'foo-bar'
- },
- // runs `./jobs/some-other-path.js` on start
- {
- name: 'beep',
- path: path.join(__dirname, 'jobs', 'some-other-path')
- },
- // runs `./jobs/worker-1.js` on the last day of the month
- {
- name: 'worker-1',
- interval: 'on the last day of the month'
- },
- // runs `./jobs/worker-2.js` every other day
- {
- name: 'worker-2',
- interval: 'every 2 days'
- },
- // runs `./jobs/worker-3.js` at 10:15am and 5:15pm every day except on Tuesday
- {
- name: 'worker-3',
- interval: 'at 10:15 am also at 5:15pm except on Tuesday'
- },
- // runs `./jobs/worker-4.js` at 10:15am every weekday
- {
- name: 'worker-4',
- cron: '15 10 ? * *',
- cronValidate: {
- override: {
- useBlankDay: true
- }
- }
- },
- // runs `./jobs/worker-5.js` on after 10 minutes have elapsed
- {
- name: 'worker-5',
- timeout: '10m'
- },
- // runs `./jobs/worker-6.js` after 1 minute and every 5 minutes thereafter
- {
- name: 'worker-6',
- timeout: '1m',
- interval: '5m'
- // this is unnecessary but shows you can pass a Number (ms)
- // interval: ms('5m')
- },
- // runs `./jobs/worker-7.js` after 3 days and 4 hours
- {
- name: 'worker-7',
- // this example uses `human-interval` parsing
- timeout: '3 days and 4 hours'
- },
- // runs `./jobs/worker-8.js` at midnight (once)
- {
- name: 'worker-8',
- timeout: 'at 12:00 am'
- },
- // runs `./jobs/worker-9.js` every day at midnight
- {
- name: 'worker-9',
- interval: 'at 12:00 am'
- },
- // runs `./jobs/worker-10.js` at midnight on the 1st of every month
- {
- name: 'worker-10',
- cron: '0 0 1 * *'
- },
- // runs `./jobs/worker-11.js` at midnight on the last day of month
- {
- name: 'worker-11',
- cron: '0 0 L * *',
- cronValidate: {
- useLastDayOfMonth: true
- }
- },
- // runs `./jobs/worker-12.js` at a specific Date (e.g. in 3 days)
- {
- name: 'worker-12',
- //
- date: dayjs().add(3, 'days').toDate()
- // you can also use momentjs
- //
- // date: moment('1/1/20', 'M/D/YY').toDate()
- // you can pass Date instances (if it's in the past it will not get run)
- // date: new Date()
- },
- // runs `./jobs/worker-13.js` on start and every 2 minutes
- {
- name: 'worker-13',
- interval: '2m'
- },
- // runs `./jobs/worker-14.js` on start with custom `new Worker` options (see below)
- {
- name: 'worker-14',
- //
- worker: {
- workerData: {
- foo: 'bar',
- beep: 'boop'
- }
- }
- },
- // runs `./jobs/worker-15.js` **NOT** on start, but every 2 minutes
- {
- name: 'worker-15',
- timeout: false, // <-- specify `false` here to prevent default timeout (e.g. on start)
- interval: '2m'
- },
- // runs `./jobs/worker-16.js` on January 1st, 2022
- // and at midnight on the 1st of every month thereafter
- {
- name: 'worker-16',
- date: dayjs('1-1-2022', 'M-D-YYYY').toDate(),
- cron: '0 0 1 * *'
- }
- ]
- });
- // handle graceful reloads, pm2 support, and events like SIGHUP, SIGINT, etc.
- const graceful = new Graceful({ brees: [bree] });
- graceful.listen();
- // start all jobs (this is the equivalent of reloading a crontab):
- (async () => {
- await bree.start();
- })();
- /*
- // start only a specific job:
- (async () => {
- await bree.start('foo');
- })();
- // stop all jobs
- bree.stop();
- // stop only a specific job:
- bree.stop('beep');
- // run all jobs (this does not abide by timeout/interval/cron and spawns workers immediately)
- bree.run();
- // run a specific job (...)
- bree.run('beep');
- (async () => {
- // add a job array after initialization:
- const added = await bree.add(['boop']); // will return array of added jobs
- // this must then be started using one of the above methods
- // add a job after initialization:
- await bree.add('boop');
- // this must then be started using one of the above methods
- })();
- // remove a job after initialization:
- bree.remove('boop');
- */
- ```
Instance Options
Property | Type | Default | Description |
---|---|---|---|
----------------------- | -------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
`logger` | Object | `console` | This |
`root` | String | `path.resolve('jobs')` | Resolves |
`silenceRootCheckError` | Boolean | `false` | Silences |
`doRootCheck` | Boolean | `true` | Attempts |
`removeCompleted` | Boolean | `false` | Removes |
`timeout` | Number | `0` | Default |
`interval` | Number | `0` | Default |
`jobs` | Array | `[]` | Defaults |
`hasSeconds` | Boolean | `false` | This |
`cronValidate` | Object | `{}` | This |
`closeWorkerAfterMs` | Number | `0` | If |
`defaultRootIndex` | String | `index.js` | This |
`defaultExtension` | String | `js` | This |
`acceptedExtensions` | Array | `['.js', | This |
`worker` | Object | `{}` | These |
`outputWorkerMetadata` | Boolean | `false` | By |
`errorHandler` | Function | `null` | Set |
`workerMessageHandler` | Function | `null` | Set |
Job Options
Property | Type | Description |
---|---|---|
---------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
`name` | String | The |
`path` | String | The |
`timeout` | Number, | Sets |
`interval` | Number, | Sets |
`date` | Date | This |
`cron` | String | A |
`hasSeconds` | Boolean | Overrides |
`cronValidate` | Object | Overrides |
`closeWorkerAfterMs` | Number | Overrides |
`worker` | Object | Overrides |
`outputWorkerMetadata` | Boolean | Overrides |
Job Interval and Timeout Values
Listening for events
- ``` js
- bree.on('worker created', (name) => {
- console.log('worker created', name);
- console.log(bree.workers.get(name));
- });
- bree.on('worker deleted', (name) => {
- console.log('worker deleted', name);
- console.log(!bree.worker.has(name));
- });
- ```
Custom error/message handling
NOTE: Any console.log calls, from within the worker, will not be sent to stdout/stderr until the main thread is available. Furthermore, any console.log calls, from within the worker, will not be sent if the process is terminated before the message is printed. You should use parentPort.postMessage() alongside errorHandler or workerMessageHandler to print to stdout/stderr during worker execution. This is a known bug for workers.
- ``` js
- const logger = ('../path/to/logger');
- const errorService = ('../path/to/error-service');
- new Bree({
- jobs: [
- {
- name: 'job that sometimes throws errors',
- path: jobFunction
- }
- ],
- errorHandler: (error, workerMetadata) => {
- // workerMetadata will be populated with extended worker information only if
- // Bree instance is initialized with parameter `workerMetadata: true`
- if (workerMetadata.threadId) {
- logger.info(`There was an error while running a worker ${workerMetadata.name} with thread ID: ${workerMetadata.threadId}`)
- } else {
- logger.info(`There was an error while running a worker ${workerMetadata.name}`)
- }
- logger.error(error);
- errorService.captureException(error);
- }
- });
- ```
Cancellation, Retries, Stalled Jobs, and Graceful Reloading
- ``` js
- //
- const { parentPort } = require('worker_threads');
- // ...
- function cancel() {
- // do cleanup here
- // (if you're using @ladjs/graceful, the max time this can run by default is 5s)
- // send a message to the parent that we're ready to terminate
- // (you could do `process.exit(0)` or `process.exit(1)` instead if desired
- // but this is a bit of a cleaner approach for worker termination
- if (parentPort) parentPort.postMessage('cancelled');
- else process.exit(0);
- }
- if (parentPort)
- parentPort.once('message', message => {
- if (message === 'cancel') return cancel();
- });
- ```
Interval, Timeout, Date, and Cron Validation
Writing jobs with Promises and async-await
- ``` js
- const { parentPort } = require('worker_threads');
- const delay = require('delay');
- const ms = require('ms');
- (async () => {
- // wait for a promise to finish
- await delay(ms('10s'));
- // signal to parent that the job is done
- if (parentPort) parentPort.postMessage('done');
- else process.exit(0);
- })();
- ```
Callbacks, Done, and Completion States
Long-running jobs
Complex timeouts and intervals
Custom Worker Options
Using functions for jobs
- ``` js
- new Bree({ jobs: [someFunction] });
- ```
- ``` js
- new Bree({
- jobs: [
- {
- name: 'job with function',
- path: someFunction
- }
- ]
- });
- ```
Typescript and Usage with Bundlers
- ```tree
- - dist
- |-jobs
- |-job.js
- |-index.js
- ```
Concurrency
Plugins
- ``` js
- Bree.extend(plugin, options);
- ```
Available Plugins
Creating plugins for Bree
- ``` js
- const plugin = (options, Bree) => {
- /* plugin logic */
- };
- ```
Real-world usage
Contributors
Name | Website |
---|---|
---------------- | --------------------------------- |
**Nick | |
**shadowgate15** |