UNPKG

42.5 kBMarkdownView Raw
1<h1 align="center">
2 <a href="https://jobscheduler.net"><img src="https://d1i8ikybhfrv4r.cloudfront.net/bree/bree.png" alt="bree" /></a>
3</h1>
4<div align="center">
5 <a href="https://github.com/breejs/bree/actions/workflows/ci.yml"><img src="https://github.com/breejs/bree/actions/workflows/ci.yml/badge.svg" alt="build status" /></a>
6 <a href="https://github.com/sindresorhus/xo"><img src="https://img.shields.io/badge/code_style-XO-5ed9c7.svg" alt="code style" /></a>
7 <a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/styled_with-prettier-ff69b4.svg" alt="styled with prettier" /></a>
8 <a href="https://lass.js.org"><img src="https://img.shields.io/badge/made_with-lass-95CC28.svg" alt="made with lass" /></a>
9 <a href="LICENSE"><img src="https://img.shields.io/github/license/breejs/bree.svg" alt="license" /></a>
10 <a href="https://npm.im/bree"><img src="https://img.shields.io/npm/dt/bree.svg" alt="npm downloads" /></a>
11</div>
12<br />
13<div align="center">
14 Bree is the best job scheduler for <a href="https://nodejs.org">Node.js</a> and JavaScript with <a href="https://en.wikipedia.org/wiki/Cron">cron</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">dates</a>, <a href="https://github.com/vercel/ms">ms</a>, <a href="https://github.com/breejs/later">later</a>, and <a href="https://github.com/agenda/human-interval">human-friendly</a> support.
15</div>
16<hr />
17<div align="center">
18 Works in Node v12.17.0+, uses <a href="https://nodejs.org/api/worker_threads.html">worker threads</a> (Node.js) to spawn sandboxed processes, and supports <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async/await</a>, <a href="https://github.com/sindresorhus/p-retry">retries</a>, <a href="https://github.com/sindresorhus/p-throttle">throttling</a>, <a href="#concurrency">concurrency</a>, and <a href="#cancellation-retries-stalled-jobs-and-graceful-reloading">cancelable jobs with graceful shutdown</a>. Simple, fast, and lightweight. <strong>Made for <a href="https://forwardemail.net">Forward Email</a> and <a href="https://lad.js.org">Lad</a></strong>.
19</div>
20
21
22## Table of Contents
23
24* [Foreword](#foreword)
25* [Install](#install)
26* [Upgrading](#upgrading)
27* [Usage and Examples](#usage-and-examples)
28 * [ECMAScript modules (ESM)](#ecmascript-modules-esm)
29 * [CommonJS (CJS)](#commonjs-cjs)
30* [Instance Options](#instance-options)
31* [Job Options](#job-options)
32* [Job Interval and Timeout Values](#job-interval-and-timeout-values)
33* [Listening for events](#listening-for-events)
34* [Custom error/message handling](#custom-errormessage-handling)
35* [Cancellation, Retries, Stalled Jobs, and Graceful Reloading](#cancellation-retries-stalled-jobs-and-graceful-reloading)
36* [Interval, Timeout, Date, and Cron Validation](#interval-timeout-date-and-cron-validation)
37* [Writing jobs with Promises and async-await](#writing-jobs-with-promises-and-async-await)
38* [Callbacks, Done, and Completion States](#callbacks-done-and-completion-states)
39* [Long-running jobs](#long-running-jobs)
40* [Complex timeouts and intervals](#complex-timeouts-and-intervals)
41* [Custom Worker Options](#custom-worker-options)
42* [Using functions for jobs](#using-functions-for-jobs)
43* [Typescript and Usage with Bundlers](#typescript-and-usage-with-bundlers)
44* [Concurrency](#concurrency)
45* [Plugins](#plugins)
46 * [Available Plugins](#available-plugins)
47 * [Creating plugins for Bree](#creating-plugins-for-bree)
48* [Real-world usage](#real-world-usage)
49* [Contributors](#contributors)
50* [License](#license)
51
52
53## Foreword
54
55Bree was created to give you fine-grained control with simplicity, and has built-in support for workers, sandboxed processes, graceful reloading, cron jobs, dates, human-friendly time representations, and much more.
56
57We recommend you to query a persistent database in your jobs, to prevent specific operations from running more than once.
58
59Bree does not force you to use an additional database layer of [Redis][] or [MongoDB][] to manage job state.
60
61In doing so, you should manage boolean job states yourself using queries. For instance, if you have to send a welcome email to users, only send a welcome email to users that do not have a Date value set yet for `welcome_email_sent_at`.
62
63
64## Install
65
66[npm][]:
67
68```sh
69npm install bree
70```
71
72[yarn][]:
73
74```sh
75yarn add bree
76```
77
78
79## Upgrading
80
81To see details about upgrading from the last major version, please see [UPGRADING.md](https://github.com/breejs/bree/blob/master/UPGRADING.md).
82
83> **IMPORTANT:** [Bree v9.0.0](https://github.com/breejs/bree/releases/tag/v9.0.0) has several breaking changes, please see [UPGRADING.md](https://github.com/breejs/bree/blob/master/UPGRADING.md) for more insight.
84
85> **NOTE:** [Bree v6.5.0](https://github.com/breejs/bree/releases/tag/v6.5.0) is the last version to support Node v10 and browsers.
86
87
88## Usage and Examples
89
90The example below assumes that you have a directory `jobs` in the root of the directory from which you run this example. For example, if the example below is at `/path/to/script.js`, then `/path/to/jobs/` must also exist as a directory. If you wish to disable this feature, then pass `root: false` as an option.
91
92Inside this `jobs` directory are individual scripts which are run using [Workers][] per optional timeouts, and additionally, an optional interval or cron expression. The example below contains comments, which help to clarify how this works.
93
94The option `jobs` passed to a new instance of `Bree` (as shown below) is an Array. It contains values which can either be a String (name of a job in the `jobs` directory, which is run on boot) OR it can be an Object with `name`, `path`, `timeout`, and `interval` properties. If you do not supply a `path`, then the path is created using the root directory (defaults to `jobs`) in combination with the `name`. If you do not supply values for `timeout` and/nor `interval`, then these values are defaulted to `0` (which is the default for both, see [index.js](https://github.com/breejs/bree/blob/master/src/index.js) for more insight into configurable default options).
95
96We have also documented all [Instance Options](#instance-options) and [Job Options](#job-options) in this README below. Be sure to read those sections so you have a complete understanding of how Bree works.
97
98### ECMAScript modules (ESM)
99
100```js
101// app.mjs
102
103import Bree from 'bree';
104
105const bree = new Bree({
106 // ... (see below) ...
107});
108
109// top-level await supported in Node v14.8+
110await bree.start();
111
112// ... (see below) ...
113```
114
115**Please reference the [#CommonJS](#commonjs-cjs) example below for more insight and options.**
116
117### CommonJS (CJS)
118
119```js
120// app.js
121
122const path = require('path');
123
124// optional
125const ms = require('ms');
126const dayjs = require('dayjs');
127const Graceful = require('@ladjs/graceful');
128const Cabin = require('cabin');
129
130// required
131const Bree = require('bree');
132
133//
134// NOTE: see the "Instance Options" section below in this README
135// for the complete list of options and their defaults
136//
137const bree = new Bree({
138 //
139 // NOTE: by default the `logger` is set to `console`
140 // however we recommend you to use CabinJS as it
141 // will automatically add application and worker metadata
142 // to your log output, and also masks sensitive data for you
143 // <https://cabinjs.com>
144 //
145 // NOTE: You can also pass `false` as `logger: false` to disable logging
146 //
147 logger: new Cabin(),
148
149 //
150 // NOTE: instead of passing this Array as an option
151 // you can create a `./jobs/index.js` file, exporting
152 // this exact same array as `module.exports = [ ... ]`
153 // doing so will allow you to keep your job configuration and the jobs
154 // themselves all in the same folder and very organized
155 //
156 // See the "Job Options" section below in this README
157 // for the complete list of job options and configurations
158 //
159 jobs: [
160 // runs `./jobs/foo.js` on start
161 'foo',
162
163 // runs `./jobs/foo-bar.js` on start
164 {
165 name: 'foo-bar'
166 },
167
168 // runs `./jobs/some-other-path.js` on start
169 {
170 name: 'beep',
171 path: path.join(__dirname, 'jobs', 'some-other-path')
172 },
173
174 // runs `./jobs/worker-1.js` on the last day of the month
175 {
176 name: 'worker-1',
177 interval: 'on the last day of the month'
178 },
179
180 // runs `./jobs/worker-2.js` every other day
181 {
182 name: 'worker-2',
183 interval: 'every 2 days'
184 },
185
186 // runs `./jobs/worker-3.js` at 10:15am and 5:15pm every day except on Tuesday
187 {
188 name: 'worker-3',
189 interval: 'at 10:15 am also at 5:15pm except on Tuesday'
190 },
191
192 // runs `./jobs/worker-4.js` at 10:15am every weekday
193 {
194 name: 'worker-4',
195 cron: '15 10 ? * *',
196 cronValidate: {
197 override: {
198 useBlankDay: true
199 }
200 }
201 },
202
203 // runs `./jobs/worker-5.js` on after 10 minutes have elapsed
204 {
205 name: 'worker-5',
206 timeout: '10m'
207 },
208
209 // runs `./jobs/worker-6.js` after 1 minute and every 5 minutes thereafter
210 {
211 name: 'worker-6',
212 timeout: '1m',
213 interval: '5m'
214 // this is unnecessary but shows you can pass a Number (ms)
215 // interval: ms('5m')
216 },
217
218 // runs `./jobs/worker-7.js` after 3 days and 4 hours
219 {
220 name: 'worker-7',
221 // this example uses `human-interval` parsing
222 timeout: '3 days and 4 hours'
223 },
224
225 // runs `./jobs/worker-8.js` at midnight (once)
226 {
227 name: 'worker-8',
228 timeout: 'at 12:00 am'
229 },
230
231 // runs `./jobs/worker-9.js` every day at midnight
232 {
233 name: 'worker-9',
234 interval: 'at 12:00 am'
235 },
236
237 // runs `./jobs/worker-10.js` at midnight on the 1st of every month
238 {
239 name: 'worker-10',
240 cron: '0 0 1 * *'
241 },
242
243 // runs `./jobs/worker-11.js` at midnight on the last day of month
244 {
245 name: 'worker-11',
246 cron: '0 0 L * *',
247 cronValidate: {
248 useLastDayOfMonth: true
249 }
250 },
251
252 // runs `./jobs/worker-12.js` at a specific Date (e.g. in 3 days)
253 {
254 name: 'worker-12',
255 // <https://github.com/iamkun/dayjs>
256 date: dayjs().add(3, 'days').toDate()
257 // you can also use momentjs
258 // <https://momentjs.com/>
259 // date: moment('1/1/20', 'M/D/YY').toDate()
260 // you can pass Date instances (if it's in the past it will not get run)
261 // date: new Date()
262 },
263
264 // runs `./jobs/worker-13.js` on start and every 2 minutes
265 {
266 name: 'worker-13',
267 interval: '2m'
268 },
269
270 // runs `./jobs/worker-14.js` on start with custom `new Worker` options (see below)
271 {
272 name: 'worker-14',
273 // <https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options>
274 worker: {
275 workerData: {
276 foo: 'bar',
277 beep: 'boop'
278 }
279 }
280 },
281
282 // runs `./jobs/worker-15.js` **NOT** on start, but every 2 minutes
283 {
284 name: 'worker-15',
285 timeout: false, // <-- specify `false` here to prevent default timeout (e.g. on start)
286 interval: '2m'
287 },
288
289 // runs `./jobs/worker-16.js` on January 1st, 2022
290 // and at midnight on the 1st of every month thereafter
291 {
292 name: 'worker-16',
293 date: dayjs('1-1-2022', 'M-D-YYYY').toDate(),
294 cron: '0 0 1 * *'
295 }
296 ]
297});
298
299// handle graceful reloads, pm2 support, and events like SIGHUP, SIGINT, etc.
300const graceful = new Graceful({ brees: [bree] });
301graceful.listen();
302
303// start all jobs (this is the equivalent of reloading a crontab):
304(async () => {
305 await bree.start();
306})();
307
308/*
309// start only a specific job:
310(async () => {
311 await bree.start('foo');
312})();
313
314// stop all jobs
315bree.stop();
316
317// stop only a specific job:
318bree.stop('beep');
319
320// run all jobs (this does not abide by timeout/interval/cron and spawns workers immediately)
321bree.run();
322
323// run a specific job (...)
324bree.run('beep');
325
326(async () => {
327 // add a job array after initialization:
328 const added = await bree.add(['boop']); // will return array of added jobs
329 // this must then be started using one of the above methods
330
331 // add a job after initialization:
332 await bree.add('boop');
333 // this must then be started using one of the above methods
334})();
335
336
337// remove a job after initialization:
338bree.remove('boop');
339*/
340```
341
342For more examples - including setting up bree with TypeScript, ESModules, and implementing an Email Queue, see the [examples](./examples) folder.
343
344For a more complete demo using express see: [Bree Express Demo](https://github.com/breejs/express-example)
345
346
347## Instance Options
348
349Here is the full list of options and their defaults. See [src/index.js](https://github.com/breejs/bree/blob/master/src/index.js) for more insight if necessary.
350
351| Property | Type | Default Value | Description |
352| ----------------------- | -------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
353| `logger` | Object | `console` | This is the default logger. **We recommend using [Cabin][cabin]** instead of using `console` as your default logger. Set this value to `false` to disable logging entirely (uses noop function) |
354| `root` | String | `path.resolve('jobs')` | Resolves a jobs folder relative to where the project is ran (the directory you call `node` in). Set this value to `false` to prevent requiring a root directory of jobs (e.g. if your jobs are not all in one directory). Set this to `path.join(__dirname, 'jobs')` to keep your jobs directory relative to the file where Bree is set up. |
355| `silenceRootCheckError` | Boolean | `false` | Silences errors from requiring the root folder. Set this to `false` if you do not want to see errors from this operation |
356| `doRootCheck` | Boolean | `true` | Attempts to `require` the root directory, when `jobs` is empty or `null`. Set this to `false` to prevent requiring the root directory |
357| `removeCompleted` | Boolean | `false` | Removes job upon completion. Set this to `true` in order to remove jobs from the array upon completion. |
358| `timeout` | Number | `0` | Default timeout for jobs (e.g. a value of `0` means that jobs will start on boot by default unless a job has a property of `timeout` or `interval` defined. Set this to `false` if you do not wish for a default value to be set for jobs. **This value does not apply to jobs with a property of `date`.** |
359| `interval` | Number | `0` | Default interval for jobs (e.g. a value of `0` means that there is no interval, and a value greater than zero indicates a default interval will be set with this value). **This value does not apply to jobs with a property of `cron`**. |
360| `jobs` | Array | `[]` | Defaults to an empty Array, but if the `root` directory has a `index.js` file, then it will be used. This allows you to keep your jobs and job definition index in the same place. See [Job Options](#job-options) below, and [Usage and Examples](#usage-and-examples) above for more insight. |
361| `hasSeconds` | Boolean | `false` | This value is passed to `later` for parsing jobs, and can be overridden on a per job basis. See [later cron parsing](https://breejs.github.io/later/parsers.html#cron) documentation for more insight. Note that setting this to `true` will automatically set `cronValidate` defaults to have `{ preset: 'default', override: { useSeconds: true } }` |
362| `cronValidate` | Object | `{}` | This value is passed to `cron-validate` for validation of cron expressions. See the [cron-validate](https://github.com/Airfooox/cron-validate) documentation for more insight. |
363| `closeWorkerAfterMs` | Number | `0` | If you set a value greater than `0` here, then it will terminate workers after this specified time (in milliseconds). **As of v6.0.0, workers now terminate after they have been signaled as "online" (as opposed to previous versions which did not take this into account and started the timer when jobs were initially "run").** By default there is no termination done, and jobs can run for infinite periods of time. |
364| `defaultRootIndex` | String | `index.js` | This value should be the file name inside of the `root` directory option (if you pass a `root` directory or use the default `root` String value (and your index file name is different than `index.js`). |
365| `defaultExtension` | String | `js` | This value can either be `js` or `mjs`. The default is `js`, and is the default extension added to jobs that are simply defined with a name and without a path. For example, if you define a job `test`, then it will look for `/path/to/root/test.js` as the file used for workers. |
366| `acceptedExtensions` | Array | `['.js', '.mjs']` | This defines all of the accepted extensions for file validation and job creation. Please note if you add to this list you must override the `createWorker` function to properly handle the new file types. |
367| `worker` | Object | `{}` | These are default options to pass when creating a `new Worker` instance. See the [Worker class](https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options) documentation for more insight. |
368| `outputWorkerMetadata` | Boolean | `false` | By default worker metadata is not passed to the second Object argument of `logger`. However if you set this to `true`, then `logger` will be invoked internally with two arguments (e.g. `logger.info('...', { worker: ... })`). This `worker` property contains `isMainThread` (Boolean), `resourceLimits` (Object), and `threadId` (String) properties; all of which correspond to [Workers][] metadata. This can be overridden on a per job basis. |
369| `errorHandler` | Function | `null` | Set this function to receive a callback when an error is encountered during worker execution (e.g. throws an exception) or when it exits with non-zero code (e.g. `process.exit(1)`). The callback receives two parameters `error` and `workerMetadata`. Important note, when this callback is present default error logging will not be executed. |
370| `workerMessageHandler` | Function | `null` | Set this function to receive a callback when a worker sends a message through [parentPort.postMessage](https://nodejs.org/docs/latest-v14.x/api/worker_threads.html#worker_threads_port_postmessage_value_transferlist). The callback receives at least two parameters `name` (of the worker) and `message` (coming from `postMessage`), if `outputWorkerMetadata` is enabled additional metadata will be sent to this handler. |
371
372
373## Job Options
374
375See [Interval, Timeout, Date, and Cron Validate](#interval-timeout-date-and-cron-validation) below for more insight besides this table:
376
377| Property | Type | Description |
378| ---------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
379| `name` | String | The name of the job. This should match the base file path (e.g. `foo` if `foo.js` is located at `/path/to/jobs/foo.js`) unless `path` option is specified. A value of `index`, `index.js`, and `index.mjs` are reserved values and cannot be used here. |
380| `path` | String | The path of the job or function used for spawning a new [Worker][workers] with. If not specified, then it defaults to the value for `name` plus the default file extension specified under [Instance Options](#instance-options). |
381| `timeout` | Number, Object, String, or Boolean | Sets the duration in milliseconds before the job starts (it overrides the default inherited `timeout` as set in [Instance Options](#instance-options). A value of `0` indicates it will start immediately. This value can be a Number, String, or a Boolean of `false` (which indicates it will NOT inherit the default `timeout` from [Instance Options](#instance-options)). See [Job Interval and Timeout Values](#job-interval-and-timeout-values) below for more insight into how this value is parsed. |
382| `interval` | Number, Object, or String | Sets the duration in milliseconds for the job to repeat itself, otherwise known as its interval (it overrides the default inherited `interval` as set in [Instance Options](#instance-options)). A value of `0` indicates it will not repeat and there will be no interval. If the value is greater than `0` then this value will be used as the interval. See [Job Interval and Timeout Values](#job-interval-and-timeout-values) below for more insight into how this value is parsed. |
383| `date` | Date | This must be a valid JavaScript Date (we use `instance of Date` for comparison). If this value is in the past, then it is not run when jobs are started (or run manually). We recommend using [dayjs][] for creating this date, and then formatting it using the `toDate()` method (e.g. `dayjs().add('3, 'days').toDate()`). You could also use [moment][] or any other JavaScript date library, as long as you convert the value to a Date instance here. |
384| `cron` | String | A cron expression to use as the job's interval, which is validated against [cron-validate][] and parsed by [later][]. |
385| `hasSeconds` | Boolean | Overrides the [Instance Options](#instance-options) `hasSeconds` property if set. Note that setting this to `true` will automatically set `cronValidate` defaults to have `{ preset: 'default', override: { useSeconds: true } }` |
386| `cronValidate` | Object | Overrides the [Instance Options](#instance-options) `cronValidate` property if set. |
387| `closeWorkerAfterMs` | Number | Overrides the [Instance Options](#instance-options) `closeWorkerAfterMs` property if set. |
388| `worker` | Object | Overrides the [Instance Options](#instance-options) `worker` property if set. |
389| `outputWorkerMetadata` | Boolean | Overrides the [Instance Options](#instance-options) `outputWorkerMetadata` property if set. |
390
391
392## Job Interval and Timeout Values
393
394These values can include Number, Object, and String variable types:
395
396* Number values indicates the number of milliseconds for the timeout or interval
397* Object values must be a [later][] schedule object value (e.g. `later.parse.cron('15 10 * * ? *'))`)
398* String values can be either a [later][], [human-interval][], or [ms][] String values (e.g. [later][] supports Strings such as `every 5 mins`, [human-interval][] supports Strings such as `3 days and 4 hours`, and [ms][] supports Strings such as `4h` for four hours)
399
400
401## Listening for events
402
403Bree extends from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) and emits two events:
404
405* `worker created` with an argument of `name`
406* `worker deleted` with an argument of `name`
407
408If you'd like to know when your workers are created (or deleted), you can do so through this example:
409
410```js
411bree.on('worker created', (name) => {
412 console.log('worker created', name);
413 console.log(bree.workers.get(name));
414});
415
416bree.on('worker deleted', (name) => {
417 console.log('worker deleted', name);
418 console.log(!bree.worker.has(name));
419});
420```
421
422
423## Custom error/message handling
424
425If you'd like to override default behavior for worker error/message handling, provide a callback function as `errorHandler` or `workerMessageHandler` parameter when creating a Bree instance.
426
427> **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](https://github.com/nodejs/node/issues/30491) for workers.
428
429An example use-case. If you want to call an external service to record an error (like Honeybadger, Sentry, etc.) along with logging the error internally. You can do so with:
430
431```js
432const logger = ('../path/to/logger');
433const errorService = ('../path/to/error-service');
434
435new Bree({
436 jobs: [
437 {
438 name: 'job that sometimes throws errors',
439 path: jobFunction
440 }
441 ],
442 errorHandler: (error, workerMetadata) => {
443 // workerMetadata will be populated with extended worker information only if
444 // Bree instance is initialized with parameter `workerMetadata: true`
445 if (workerMetadata.threadId) {
446 logger.info(`There was an error while running a worker ${workerMetadata.name} with thread ID: ${workerMetadata.threadId}`)
447 } else {
448 logger.info(`There was an error while running a worker ${workerMetadata.name}`)
449 }
450
451 logger.error(error);
452 errorService.captureException(error);
453 }
454});
455```
456
457
458## Cancellation, Retries, Stalled Jobs, and Graceful Reloading
459
460We recommend that you listen for "cancel" event in your worker paths. Doing so will allow you to handle graceful cancellation of jobs. For example, you could use [p-cancelable][]
461
462Here's a quick example of how to do that (e.g. `./jobs/some-worker.js`):
463
464```js
465// <https://nodejs.org/api/worker_threads.html>
466const { parentPort } = require('worker_threads');
467
468// ...
469
470function cancel() {
471 // do cleanup here
472 // (if you're using @ladjs/graceful, the max time this can run by default is 5s)
473
474 // send a message to the parent that we're ready to terminate
475 // (you could do `process.exit(0)` or `process.exit(1)` instead if desired
476 // but this is a bit of a cleaner approach for worker termination
477 if (parentPort) parentPort.postMessage('cancelled');
478 else process.exit(0);
479}
480
481if (parentPort)
482 parentPort.once('message', message => {
483 if (message === 'cancel') return cancel();
484 });
485```
486
487If you'd like jobs to retry, simply wrap your usage of promises with [p-retry][].
488
489We leave it up to you to have as much fine-grained control as you wish.
490
491See [@ladjs/graceful][lad-graceful] for more insight into how this package works.
492
493
494## Interval, Timeout, Date, and Cron Validation
495
496If you need help writing cron expressions, you can reference [crontab.guru](https://crontab.guru/).
497
498We support [later][], [human-interval][], or [ms][] String values for both `timeout` and `interval`.
499
500If you pass a `cron` property, then it is validated against [cron-validate][].
501
502You can pass a Date as the `date` property, but you cannot combine both `date` and `timeout`.
503
504If you do pass a Date, then it is only run if it is in the future.
505
506See [Job Interval and Timeout Values](#job-interval-and-timeout-values) above for more insight.
507
508
509## Writing jobs with Promises and async-await
510
511If jobs are running with Node pre-[v14.8.0](https://nodejs.org/en/blog/release/v14.8.0/), which [enables top-level async-await](https://github.com/nodejs/node/commit/62bb2e757f) support, here is the working alternative:
512
513```js
514const { parentPort } = require('worker_threads');
515
516const delay = require('delay');
517const ms = require('ms');
518
519(async () => {
520 // wait for a promise to finish
521 await delay(ms('10s'));
522
523 // signal to parent that the job is done
524 if (parentPort) parentPort.postMessage('done');
525 else process.exit(0);
526})();
527```
528
529
530## Callbacks, Done, and Completion States
531
532To close out the worker and signal that it is done, you can simply `parentPort.postMessage('done');` and/or `process.exit(0)`.
533
534While writing your jobs (which will run in [worker][workers] threads), you should do one of the following:
535
536* Signal to the main thread that the process has completed by sending a "done" message (per the example above in [Writing jobs with Promises and async-await](#writing-jobs-with-promises-and-async-await))
537* Exit the process if there is NOT an error with code `0` (e.g. `process.exit(0);`)
538* Throw an error if an error occurs (this will bubble up to the worker event error listener and terminate it)
539* Exit the process if there IS an error with code `1` (e.g. `process.exit(1)`)
540
541
542## Long-running jobs
543
544If a job is already running, a new worker thread will not be spawned, instead `logger.error` will be invoked with an error message (no error will be thrown, don't worry). This is to prevent bad practices from being used. If you need something to be run more than one time, then make the job itself run the task multiple times. This approach gives you more fine-grained control.
545
546By default, workers run indefinitely and are not closed until they exit (e.g. via `process.exit(0)` or `process.exit(1)`, OR send to the parent port a "close" message, which will subsequently call `worker.close()` to close the worker thread.
547
548If you wish to specify a maximum time (in milliseconds) that a worker can run, then pass `closeWorkerAfterMs` (Number) either as a default option when creating a `new Bree()` instance (e.g. `new Bree({ closeWorkerAfterMs: ms('10s') })`) or on a per-job configuration, e.g. `{ name: 'beep', closeWorkerAfterMs: ms('5m') }`.
549
550As of v6.0.0 when you pass `closeWorkerAfterMs`, the timer will start once the worker is signaled as "online" (as opposed to previous versions which did not take this into account).
551
552
553## Complex timeouts and intervals
554
555Since we use [later][], you can pass an instance of `later.parse.recur`, `later.parse.cron`, or `later.parse.text` as the `timeout` or `interval` property values (e.g. if you need to construct something manually).
556
557You can also use [dayjs][] to construct dates (e.g. from now or a certain date) to millisecond differences using `dayjs().diff(new Date(), 'milliseconds')`. You would then pass that returned Number value as `timeout` or `interval` as needed.
558
559
560## Custom Worker Options
561
562You can pass a default worker configuration object as `new Bree({ worker: { ... } });`.
563
564These options are passed to the `options` argument when we internally invoke `new Worker(path, options)`.
565
566Additionally, you can pass custom worker options on a per-job basis through a `worker` property Object on the job definition.
567
568See [complete documentation](https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options) for options (but you usually don't have to modify these).
569
570
571## Using functions for jobs
572
573It is highly recommended to use files instead of functions. However, sometimes it is necessary to use functions.
574
575You can pass a function to be run as a job:
576
577```js
578new Bree({ jobs: [someFunction] });
579```
580
581(or)
582
583```js
584new Bree({
585 jobs: [
586 {
587 name: 'job with function',
588 path: someFunction
589 }
590 ]
591});
592```
593
594The function will be run as if it's in its own file, therefore no variables or dependencies will be shared from the local context by default.
595
596You should be able to pass data via `worker.workerData` (see [Custom Worker Options](#custom-worker-options)).
597
598Note that you cannot pass a built-in nor bound function.
599
600
601## Typescript and Usage with Bundlers
602
603When working with a bundler or a tool that transpiles your code in some form or another, we recommend that your bundler is set up in a way that transforms both your application code and your jobs. Because your jobs are in their own files and are run in their own separate threads, they will not be part of your applications dependency graph and need to be setup as their own entry points. You need to ensure you have configured your tool to bundle your jobs into a jobs folder and keep them properly relative to your entry point folder.
604
605We recommend setting the `root` instance options to `path.join(__dirname,'jobs')` so that bree searches for your jobs folder relative to the file being ran. (by default it searches for jobs relative to where `node` is invoked). We recommend treating each job as an entry point and running all jobs through the same transformations as your app code.
606
607After an example transformation - you should expect the output in your `dist` folder to look like:
608
609```tree
610- dist
611 |-jobs
612 |-job.js
613 |-index.js
614```
615
616For some example TypeScript set ups - see the [examples folder](https://github.com/breejs/bree/tree/master/examples).
617
618For another alternative also see the [@breejs/ts-worker](https://github.com/breejs/ts-worker) plugin.
619
620
621## Concurrency
622
623We recommend using the following packages in your workers for handling concurrency:
624
625* <https://github.com/sindresorhus/p-all>
626* <https://github.com/sindresorhus/p-limit>
627* <https://github.com/sindresorhus/p-queue>
628* <https://github.com/sindresorhus/p-map>
629
630
631## Plugins
632
633Plugins can be added to Bree using a similar method to [Day.js](https://day.js.org/)
634
635To add a plugin use the following method:
636
637```js
638Bree.extend(plugin, options);
639```
640
641### Available Plugins
642
643* [API](https://github.com/breejs/api)
644* [TypeScript Worker](https://github.com/breejs/ts-worker)
645
646### Creating plugins for Bree
647
648Plugins should be a function that recieves an `options` object and the `Bree` class:
649
650```js
651 const plugin = (options, Bree) => {
652 /* plugin logic */
653 };
654```
655
656
657## Real-world usage
658
659More detailed examples can be found in [Forward Email][forward-email], [Lad][], and [Ghost][ghost].
660
661
662## Contributors
663
664| Name | Website |
665| ---------------- | --------------------------------- |
666| **Nick Baugh** | <http://niftylettuce.com/> |
667| **shadowgate15** | <https://github.com/shadowgate15> |
668
669
670## License
671
672[MIT](LICENSE) © [Nick Baugh](http://niftylettuce.com/)
673
674
675##
676
677<a href="#"><img src="https://d1i8ikybhfrv4r.cloudfront.net/bree/footer.png" alt="#" /></a>
678
679[ms]: https://github.com/vercel/ms
680
681[human-interval]: https://github.com/agenda/human-interval
682
683[npm]: https://www.npmjs.com/
684
685[yarn]: https://yarnpkg.com/
686
687[workers]: https://nodejs.org/api/worker_threads.html
688
689[lad]: https://lad.js.org
690
691[p-retry]: https://github.com/sindresorhus/p-retry
692
693[p-cancelable]: https://github.com/sindresorhus/p-cancelable
694
695[later]: https://breejs.github.io/later/parsers.html
696
697[cron-validate]: https://github.com/Airfooox/cron-validate
698
699[forward-email]: https://github.com/forwardemail/forwardemail.net
700
701[dayjs]: https://github.com/iamkun/dayjs
702
703[redis]: https://redis.io/
704
705[mongodb]: https://www.mongodb.com/
706
707[lad-graceful]: https://github.com/ladjs/graceful
708
709[cabin]: https://cabinjs.com
710
711[moment]: https://momentjs.com
712
713[ghost]: https://ghost.org/