UNPKG

41.2 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/later/actions/workflows/ci.yml/badge.svg" alt="build status" /></a>
6 <a href="https://codecov.io/github/breejs/bree"><img src="https://img.shields.io/codecov/c/github/breejs/bree/master.svg" alt="code coverage" /></a>
7 <a href="https://github.com/sindresorhus/xo"><img src="https://img.shields.io/badge/code_style-XO-5ed9c7.svg" alt="code style" /></a>
8 <a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/styled_with-prettier-ff69b4.svg" alt="styled with prettier" /></a>
9 <a href="https://lass.js.org"><img src="https://img.shields.io/badge/made_with-lass-95CC28.svg" alt="made with lass" /></a>
10 <a href="LICENSE"><img src="https://img.shields.io/github/license/breejs/bree.svg" alt="license" /></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.11.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* [Instance Options](#instance-options)
29* [Job Options](#job-options)
30* [Job Interval and Timeout Values](#job-interval-and-timeout-values)
31* [Listening for events](#listening-for-events)
32* [Custom error/message handling](#custom-errormessage-handling)
33* [Cancellation, Retries, Stalled Jobs, and Graceful Reloading](#cancellation-retries-stalled-jobs-and-graceful-reloading)
34* [Interval, Timeout, Date, and Cron Validation](#interval-timeout-date-and-cron-validation)
35* [Writing jobs with Promises and async-await](#writing-jobs-with-promises-and-async-await)
36* [Callbacks, Done, and Completion States](#callbacks-done-and-completion-states)
37* [Long-running jobs](#long-running-jobs)
38* [Complex timeouts and intervals](#complex-timeouts-and-intervals)
39* [Custom Worker Options](#custom-worker-options)
40* [Using functions for jobs](#using-functions-for-jobs)
41* [Typescript and Usage with Bundlers](#typescript-and-usage-with-bundlers)
42* [Concurrency](#concurrency)
43* [Plugins](#plugins)
44 * [Available Plugins](#available-plugins)
45 * [Creating plugins for Bree](#creating-plugins-for-bree)
46* [Real-world usage](#real-world-usage)
47* [Contributors](#contributors)
48* [License](#license)
49
50
51## Foreword
52
53Bree 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.
54
55We recommend you to query a persistent database in your jobs, to prevent specific operations from running more than once.
56
57Bree does not force you to use an additional database layer of [Redis][] or [MongoDB][] to manage job state.
58
59In 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`.
60
61
62## Install
63
64[npm][]:
65
66```sh
67npm install bree
68```
69
70[yarn][]:
71
72```sh
73yarn add bree
74```
75
76
77## Upgrading
78
79To see details about upgrading from the last major version, check out this [doc](./UPGRADING.md).
80
81
82## Usage and Examples
83
84The 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.
85
86Inside 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.
87
88The 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](src/index.js) for more insight into configurable default options).
89
90We 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.
91
92> **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.
93
94```js
95const path = require('path');
96
97// optional
98const ms = require('ms');
99const dayjs = require('dayjs');
100const Graceful = require('@ladjs/graceful');
101const Cabin = require('cabin');
102
103// required
104const Bree = require('bree');
105
106//
107// NOTE: see the "Instance Options" section below in this README
108// for the complete list of options and their defaults
109//
110const bree = new Bree({
111 //
112 // NOTE: by default the `logger` is set to `console`
113 // however we recommend you to use CabinJS as it
114 // will automatically add application and worker metadata
115 // to your log output, and also masks sensitive data for you
116 // <https://cabinjs.com>
117 //
118 // NOTE: You can also pass `false` as `logger: false` to disable logging
119 //
120 logger: new Cabin(),
121
122 //
123 // NOTE: instead of passing this Array as an option
124 // you can create a `./jobs/index.js` file, exporting
125 // this exact same array as `module.exports = [ ... ]`
126 // doing so will allow you to keep your job configuration and the jobs
127 // themselves all in the same folder and very organized
128 //
129 // See the "Job Options" section below in this README
130 // for the complete list of job options and configurations
131 //
132 jobs: [
133 // runs `./jobs/foo.js` on start
134 'foo',
135
136 // runs `./jobs/foo-bar.js` on start
137 {
138 name: 'foo-bar'
139 },
140
141 // runs `./jobs/some-other-path.js` on start
142 {
143 name: 'beep',
144 path: path.join(__dirname, 'jobs', 'some-other-path')
145 },
146
147 // runs `./jobs/worker-1.js` on the last day of the month
148 {
149 name: 'worker-1',
150 interval: 'on the last day of the month'
151 },
152
153 // runs `./jobs/worker-2.js` every other day
154 {
155 name: 'worker-2',
156 interval: 'every 2 days'
157 },
158
159 // runs `./jobs/worker-3.js` at 10:15am and 5:15pm every day except on Tuesday
160 {
161 name: 'worker-3',
162 interval: 'at 10:15 am also at 5:15pm except on Tuesday'
163 },
164
165 // runs `./jobs/worker-4.js` at 10:15am every weekday
166 {
167 name: 'worker-4',
168 cron: '15 10 ? * *',
169 cronValidate: {
170 override: {
171 useBlankDay: true
172 }
173 }
174 },
175
176 // runs `./jobs/worker-5.js` on after 10 minutes have elapsed
177 {
178 name: 'worker-5',
179 timeout: '10m'
180 },
181
182 // runs `./jobs/worker-6.js` after 1 minute and every 5 minutes thereafter
183 {
184 name: 'worker-6',
185 timeout: '1m',
186 interval: '5m'
187 // this is unnecessary but shows you can pass a Number (ms)
188 // interval: ms('5m')
189 },
190
191 // runs `./jobs/worker-7.js` after 3 days and 4 hours
192 {
193 name: 'worker-7',
194 // this example uses `human-interval` parsing
195 timeout: '3 days and 4 hours'
196 },
197
198 // runs `./jobs/worker-8.js` at midnight (once)
199 {
200 name: 'worker-8',
201 timeout: 'at 12:00 am'
202 },
203
204 // runs `./jobs/worker-9.js` every day at midnight
205 {
206 name: 'worker-9',
207 interval: 'at 12:00 am'
208 },
209
210 // runs `./jobs/worker-10.js` at midnight on the 1st of every month
211 {
212 name: 'worker-10',
213 cron: '0 0 1 * *'
214 },
215
216 // runs `./jobs/worker-11.js` at midnight on the last day of month
217 {
218 name: 'worker-11',
219 cron: '0 0 L * *',
220 cronValidate: {
221 useLastDayOfMonth: true
222 }
223 },
224
225 // runs `./jobs/worker-12.js` at a specific Date (e.g. in 3 days)
226 {
227 name: 'worker-12',
228 // <https://github.com/iamkun/dayjs>
229 date: dayjs().add(3, 'days').toDate()
230 // you can also use momentjs
231 // <https://momentjs.com/>
232 // date: moment('1/1/20', 'M/D/YY').toDate()
233 // you can pass Date instances (if it's in the past it will not get run)
234 // date: new Date()
235 },
236
237 // runs `./jobs/worker-13.js` on start and every 2 minutes
238 {
239 name: 'worker-13',
240 interval: '2m'
241 },
242
243 // runs `./jobs/worker-14.js` on start with custom `new Worker` options (see below)
244 {
245 name: 'worker-14',
246 // <https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options>
247 worker: {
248 workerData: {
249 foo: 'bar',
250 beep: 'boop'
251 }
252 }
253 },
254
255 // runs `./jobs/worker-15.js` **NOT** on start, but every 2 minutes
256 {
257 name: 'worker-15',
258 timeout: false, // <-- specify `false` here to prevent default timeout (e.g. on start)
259 interval: '2m'
260 },
261
262 // runs `./jobs/worker-16.js` on January 1st, 2022
263 // and at midnight on the 1st of every month thereafter
264 {
265 name: 'worker-16',
266 date: dayjs('1-1-2022', 'M-D-YYYY').toDate(),
267 cron: '0 0 1 * *'
268 }
269 ]
270});
271
272// handle graceful reloads, pm2 support, and events like SIGHUP, SIGINT, etc.
273const graceful = new Graceful({ brees: [bree] });
274graceful.listen();
275
276// start all jobs (this is the equivalent of reloading a crontab):
277bree.start();
278
279/*
280// start only a specific job:
281bree.start('foo');
282
283// stop all jobs
284bree.stop();
285
286// stop only a specific job:
287bree.stop('beep');
288
289// run all jobs (this does not abide by timeout/interval/cron and spawns workers immediately)
290bree.run();
291
292// run a specific job (...)
293bree.run('beep');
294
295// add a job array after initialization:
296const added = bree.add(['boop']); // will return array of added jobs
297// this must then be started using one of the above methods
298
299// add a job after initialization:
300bree.add('boop');
301// this must then be started using one of the above methods
302
303// remove a job after initialization:
304bree.remove('boop');
305*/
306```
307
308For more examples - including setting up bree with TypeScript, ESModules, and implementing an Email Queue, see the [examples](./examples) folder.
309
310For a more complete demo using express see: [Bree Express Demo](https://github.com/breejs/express-example)
311
312
313## Instance Options
314
315Here is the full list of options and their defaults. See [index.js](index.js) for more insight if necessary.
316
317| Property | Type | Default Value | Description |
318| ----------------------- | -------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
319| `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) |
320| `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. |
321| `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 |
322| `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 |
323| `removeCompleted` | Boolean | `false` | Removes job upon completion. Set this to `true` in order to remove jobs from the array upon completion. |
324| `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`.** |
325| `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`**. |
326| `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. |
327| `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 } }` |
328| `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. |
329| `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. |
330| `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. |
331| `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. |
332| `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. |
333| `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. |
334| `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. |
335| `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. |
336
337
338## Job Options
339
340See [Interval, Timeout, Date, and Cron Validate](#interval-timeout-date-and-cron-validation) below for more insight besides this table:
341
342| Property | Type | Description |
343| ---------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
344| `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. |
345| `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). |
346| `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. |
347| `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. |
348| `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. |
349| `cron` | String | A cron expression to use as the job's interval, which is validated against [cron-validate][] and parsed by [later][]. |
350| `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 } }` |
351| `cronValidate` | Object | Overrides the [Instance Options](#instance-options) `cronValidate` property if set. |
352| `closeWorkerAfterMs` | Number | Overrides the [Instance Options](#instance-options) `closeWorkerAfterMs` property if set. |
353| `worker` | Object | Overrides the [Instance Options](#instance-options) `worker` property if set. |
354| `outputWorkerMetadata` | Boolean | Overrides the [Instance Options](#instance-options) `outputWorkerMetadata` property if set. |
355
356
357## Job Interval and Timeout Values
358
359These values can include Number, Object, and String variable types:
360
361* Number values indicates the number of milliseconds for the timeout or interval
362* Object values must be a [later][] schedule object value (e.g. `later.parse.cron('15 10 * * ? *'))`)
363* 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)
364
365
366## Listening for events
367
368Bree extends from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) and emits two events:
369
370* `worker created` with an argument of `name`
371* `worker deleted` with an argument of `name`
372
373If you'd like to know when your workers are created (or deleted), you can do so through this example:
374
375```js
376bree.on('worker created', (name) => {
377 console.log('worker created', name);
378 console.log(bree.workers[name]);
379});
380
381bree.on('worker deleted', (name) => {
382 console.log('worker deleted', name);
383 console.log(typeof bree.workers[name] === 'undefined');
384});
385```
386
387
388## Custom error/message handling
389
390If 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.
391
392> **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.
393
394An 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:
395
396```js
397const logger = ('../path/to/logger');
398const errorService = ('../path/to/error-service');
399
400new Bree({
401 jobs: [
402 {
403 name: 'job that sometimes throws errors',
404 path: jobFunction
405 }
406 ],
407 errorHandler: (error, workerMetadata) => {
408 // workerMetadata will be populated with extended worker information only if
409 // Bree instance is initialized with parameter `workerMetadata: true`
410 if (workerMetadata.threadId) {
411 logger.info(`There was an error while running a worker ${workerMetadata.name} with thread ID: ${workerMetadata.threadId}`)
412 } else {
413 logger.info(`There was an error while running a worker ${workerMetadata.name}`)
414 }
415
416 logger.error(error);
417 errorService.captureException(error);
418 }
419});
420```
421
422
423## Cancellation, Retries, Stalled Jobs, and Graceful Reloading
424
425We 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][]
426
427Here's a quick example of how to do that (e.g. `./jobs/some-worker.js`):
428
429```js
430// <https://nodejs.org/api/worker_threads.html>
431const { parentPort } = require('worker_threads');
432
433// ...
434
435function cancel() {
436 // do cleanup here
437 // (if you're using @ladjs/graceful, the max time this can run by default is 5s)
438
439 // send a message to the parent that we're ready to terminate
440 // (you could do `process.exit(0)` or `process.exit(1)` instead if desired
441 // but this is a bit of a cleaner approach for worker termination
442 if (parentPort) parentPort.postMessage('cancelled');
443 else process.exit(0);
444}
445
446if (parentPort)
447 parentPort.once('message', message => {
448 if (message === 'cancel') return cancel();
449 });
450```
451
452If you'd like jobs to retry, simply wrap your usage of promises with [p-retry][].
453
454We leave it up to you to have as much fine-grained control as you wish.
455
456See [@ladjs/graceful][lad-graceful] for more insight into how this package works.
457
458
459## Interval, Timeout, Date, and Cron Validation
460
461If you need help writing cron expressions, you can reference [crontab.guru](https://crontab.guru/).
462
463We support [later][], [human-interval][], or [ms][] String values for both `timeout` and `interval`.
464
465If you pass a `cron` property, then it is validated against [cron-validate][].
466
467You can pass a Date as the `date` property, but you cannot combine both `date` and `timeout`.
468
469If you do pass a Date, then it is only run if it is in the future.
470
471See [Job Interval and Timeout Values](#job-interval-and-timeout-values) above for more insight.
472
473
474## Writing jobs with Promises and async-await
475
476If 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:
477
478```js
479const { parentPort } = require('worker_threads');
480
481const delay = require('delay');
482const ms = require('ms');
483
484(async () => {
485 // wait for a promise to finish
486 await delay(ms('10s'));
487
488 // signal to parent that the job is done
489 if (parentPort) parentPort.postMessage('done');
490 else process.exit(0);
491})();
492```
493
494
495## Callbacks, Done, and Completion States
496
497To close out the worker and signal that it is done, you can simply `parentPort.postMessage('done');` and/or `process.exit(0)`.
498
499While writing your jobs (which will run in [worker][workers] threads), you should do one of the following:
500
501* 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))
502* Exit the process if there is NOT an error with code `0` (e.g. `process.exit(0);`)
503* Throw an error if an error occurs (this will bubble up to the worker event error listener and terminate it)
504* Exit the process if there IS an error with code `1` (e.g. `process.exit(1)`)
505
506
507## Long-running jobs
508
509If 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.
510
511By 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.
512
513If 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') }`.
514
515As 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).
516
517
518## Complex timeouts and intervals
519
520Since 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).
521
522You 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.
523
524
525## Custom Worker Options
526
527You can pass a default worker configuration object as `new Bree({ worker: { ... } });`.
528
529These options are passed to the `options` argument when we internally invoke `new Worker(path, options)`.
530
531Additionally, you can pass custom worker options on a per-job basis through a `worker` property Object on the job definition.
532
533See [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).
534
535
536## Using functions for jobs
537
538It is highly recommended to use files instead of functions. However, sometimes it is necessary to use functions.
539
540You can pass a function to be run as a job:
541
542```js
543new Bree({ jobs: [someFunction] });
544```
545
546(or)
547
548```js
549new Bree({
550 jobs: [
551 {
552 name: 'job with function',
553 path: someFunction
554 }
555 ]
556});
557```
558
559The 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.
560
561You should be able to pass data via `worker.workerData` (see [Custom Worker Options](#custom-worker-options)).
562
563Note that you cannot pass a built-in nor bound function.
564
565
566## Typescript and Usage with Bundlers
567
568When working with a bundler or a tool that transpiles your code in some form or another, we reccomend 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.
569
570We 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 reccomend treating each job as an entry point and running all jobs through the same transformations as your app code.
571
572After an example transformation - you should expect the output in your `dist` folder to look like:
573
574```tree
575- dist
576 |-jobs
577 |-job.js
578 |-index.js
579```
580
581For some example TypeScript set ups - see the [examples folder](https://github.com/breejs/bree/tree/master/examples).
582
583For another alternative also see the [@breejs/ts-worker](https://github.com/breejs/ts-worker) plugin.
584
585
586## Concurrency
587
588We recommend using the following packages in your workers for handling concurrency:
589
590* <https://github.com/sindresorhus/p-all>
591* <https://github.com/sindresorhus/p-limit>
592* <https://github.com/sindresorhus/p-queue>
593* <https://github.com/sindresorhus/p-map>
594
595
596## Plugins
597
598Plugins can be added to Bree using a similar method to [Day.js](https://day.js.org/)
599
600To add a plugin use the following method:
601
602```js
603Bree.extend(plugin, options);
604```
605
606### Available Plugins
607
608* [API](https://github.com/breejs/api)
609* [TypeScript Worker](https://github.com/breejs/ts-worker)
610
611### Creating plugins for Bree
612
613Plugins should be a function that recieves an `options` object and the `Bree` class:
614
615```js
616 const plugin = (options, Bree) => {
617 /* plugin logic */
618 };
619```
620
621
622## Real-world usage
623
624More detailed examples can be found in [Forward Email][forward-email], [Lad][], and [Ghost][ghost].
625
626
627## Contributors
628
629| Name | Website |
630| ---------------- | --------------------------------- |
631| **Nick Baugh** | <http://niftylettuce.com/> |
632| **shadowgate15** | <https://github.com/shadowgate15> |
633
634
635## License
636
637[MIT](LICENSE) © [Nick Baugh](http://niftylettuce.com/)
638
639
640##
641
642<a href="#"><img src="https://d1i8ikybhfrv4r.cloudfront.net/bree/footer.png" alt="#" /></a>
643
644[ms]: https://github.com/vercel/ms
645
646[human-interval]: https://github.com/agenda/human-interval
647
648[npm]: https://www.npmjs.com/
649
650[yarn]: https://yarnpkg.com/
651
652[workers]: https://nodejs.org/api/worker_threads.html
653
654[lad]: https://lad.js.org
655
656[p-retry]: https://github.com/sindresorhus/p-retry
657
658[p-cancelable]: https://github.com/sindresorhus/p-cancelable
659
660[later]: https://breejs.github.io/later/parsers.html
661
662[cron-validate]: https://github.com/Airfooox/cron-validate
663
664[forward-email]: https://github.com/forwardemail/forwardemail.net
665
666[dayjs]: https://github.com/iamkun/dayjs
667
668[redis]: https://redis.io/
669
670[mongodb]: https://www.mongodb.com/
671
672[lad-graceful]: https://github.com/ladjs/graceful
673
674[cabin]: https://cabinjs.com
675
676[moment]: https://momentjs.com
677
678[ghost]: https://ghost.org/