Socket
Socket
Sign inDemoInstall

bree

Package Overview
Dependencies
Maintainers
4
Versions
100
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bree - npm Package Compare versions

Comparing version 7.2.0 to 8.0.0

2

package.json
{
"name": "bree",
"description": "The best job scheduler for Node.js and JavaScript with cron, dates, ms, later, and human-friendly support. Works in Node v12.11.0+, uses worker threads to spawn sandboxed processes, and supports async/await, retries, throttling, concurrency, and cancelable promises (graceful shutdown). Simple, fast, and lightweight. Made for Forward Email and Lad.",
"version": "7.2.0",
"version": "8.0.0",
"author": "Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)",

@@ -6,0 +6,0 @@ "ava": {

@@ -29,2 +29,3 @@ <h1 align="center">

* [Install](#install)
* [Upgrading](#upgrading)
* [Usage and Examples](#usage-and-examples)

@@ -81,2 +82,7 @@ * [Instance Options](#instance-options)

## Upgrading
To see details about upgrading from the last major version, check out this [doc](./UPGRADING.md).
## Usage and Examples

@@ -315,21 +321,21 @@

| Property | Type | Default Value | Description |
| ----------------------- | -------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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) |
| `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. |
| `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 |
| `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 |
| `removeCompleted` | Boolean | `false` | Removes job upon completion. Set this to `true` in order to remove jobs from the array upon completion. |
| `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`.** |
| `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`**. |
| `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. |
| `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 } }` |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| Property | Type | Default Value | Description |
| ----------------------- | -------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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) |
| `root` | String | `path.join(__dirname, '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. |
| `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 |
| `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 |
| `removeCompleted` | Boolean | `false` | Removes job upon completion. Set this to `true` in order to remove jobs from the array upon completion. |
| `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`.** |
| `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`**. |
| `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. |
| `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 } }` |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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,0 +342,0 @@

const fs = require('fs');
const EventEmitter = require('events');
const { Worker } = require('worker_threads');
const { resolve } = require('path');
const { join } = require('path');

@@ -33,3 +33,3 @@ const combineErrors = require('combine-errors');

// (e.g. if your jobs are not all in one directory)
root: resolve('jobs'),
root: join(__dirname, 'jobs'),
// Set this to `true` to silence root check error log

@@ -151,6 +151,6 @@ silenceRootCheckError: false,

this.closeWorkerAfterMs = {};
this.workers = {};
this.timeouts = {};
this.intervals = {};
this.closeWorkerAfterMs = new Map();
this.workers = new Map();
this.timeouts = new Map();
this.intervals = new Map();

@@ -275,12 +275,16 @@ this.isSchedule = isSchedule;

return this.workers[name]
? {
...meta,
worker: {
isMainThread: this.workers[name].isMainThread,
resourceLimits: this.workers[name].resourceLimits,
threadId: this.workers[name].threadId
}
if (this.workers.has(name)) {
const worker = this.workers.get(name);
return {
...meta,
worker: {
isMainThread: worker.isMainThread,
resourceLimits: worker.resourceLimits,
threadId: worker.threadId
}
: meta;
};
}
return meta;
}

@@ -296,7 +300,8 @@

if (this.workers[name]) {
return this.config.logger.warn(
if (this.workers.has(name)) {
this.config.logger.warn(
new Error(`Job "${name}" is already running`),
this.getWorkerMetadata(name)
);
return;
}

@@ -316,3 +321,3 @@

};
this.workers[name] = this.createWorker(job.path, object);
this.workers.set(name, this.createWorker(job.path, object));
this.emit('worker created', name);

@@ -322,3 +327,3 @@ debug('worker started', name);

const prefix = `Worker for job "${name}"`;
this.workers[name].on('online', () => {
this.workers.get(name).on('online', () => {
// If we specified a value for `closeWorkerAfterMs`

@@ -331,9 +336,12 @@ // then we need to terminate it after that execution time

debug('worker has close set', name, closeWorkerAfterMs);
this.closeWorkerAfterMs[name] = setTimeout(() => {
/* istanbul ignore else */
if (this.workers[name]) {
debug('worker has been terminated', name);
this.workers[name].terminate();
}
}, closeWorkerAfterMs);
this.closeWorkerAfterMs.set(
name,
setTimeout(() => {
/* istanbul ignore else */
if (this.workers.has(name)) {
debug('worker has been terminated', name);
this.workers.get(name).terminate();
}
}, closeWorkerAfterMs)
);
}

@@ -346,3 +354,3 @@

});
this.workers[name].on('message', (message) => {
this.workers.get(name).on('message', (message) => {
const metadata = this.getWorkerMetadata(name, { message });

@@ -362,6 +370,7 @@

if (message === 'done') {
this.workers[name].removeAllListeners('message');
this.workers[name].removeAllListeners('exit');
this.workers[name].terminate();
delete this.workers[name];
const worker = this.workers.get(name);
worker.removeAllListeners('message');
worker.removeAllListeners('exit');
worker.terminate();
this.workers.delete(name);

@@ -376,3 +385,3 @@ this.handleJobCompletion(name);

/* istanbul ignore next */
this.workers[name].on('messageerror', (err) => {
this.workers.get(name).on('messageerror', (err) => {
if (this.config.errorHandler) {

@@ -390,3 +399,3 @@ this.config.errorHandler(err, {

});
this.workers[name].on('error', (err) => {
this.workers.get(name).on('error', (err) => {
if (this.config.errorHandler) {

@@ -404,3 +413,3 @@ this.config.errorHandler(err, {

});
this.workers[name].on('exit', (code) => {
this.workers.get(name).on('exit', (code) => {
const level = code === 0 ? 'info' : 'error';

@@ -422,3 +431,3 @@ if (level === 'error' && this.config.errorHandler) {

delete this.workers[name];
this.workers.delete(name);

@@ -445,6 +454,8 @@ this.handleJobCompletion(name);

if (this.timeouts[name] || this.intervals[name] || this.workers[name]) {
return this.config.logger.warn(
new Error(`Job "${name}" is already started`)
);
if (
this.timeouts.has(name) ||
this.intervals.has(name) ||
this.workers.has(name)
) {
throw new Error(`Job "${name}" is already started`);
}

@@ -459,54 +470,37 @@

debug('job date was in the past');
// not throwing an error so that jobs can be set with a specifc date
// and only run on that date then never run again without changing config
this.config.logger.warn(
`Job "${name}" was skipped because it was in the past.`
);
this.emit('job past', name);
return;
}
this.timeouts[name] = setTimeout(() => {
this.run(name);
if (this.isSchedule(job.interval)) {
debug('job.interval is schedule', job);
this.intervals[name] = later.setInterval(
() => this.run(name),
job.interval,
job.timezone
);
} else if (Number.isFinite(job.interval) && job.interval > 0) {
debug('job.interval is finite', job);
this.intervals[name] = setInterval(
() => this.run(name),
job.interval
);
} else {
debug('job.date was scheduled to run only once', job);
}
delete this.timeouts[name];
}, job.date.getTime() - Date.now());
return;
}
// This is only complex because both timeout and interval can be a schedule
if (this.isSchedule(job.timeout)) {
debug('job timeout is schedule', job);
this.timeouts[name] = later.setTimeout(
() => {
this.timeouts.set(
name,
setTimeout(() => {
this.run(name);
if (this.isSchedule(job.interval)) {
debug('job.interval is schedule', job);
this.intervals[name] = later.setInterval(
() => this.run(name),
job.interval,
job.timezone
this.intervals.set(
name,
later.setInterval(
() => this.run(name),
job.interval,
job.timezone
)
);
} else if (Number.isFinite(job.interval) && job.interval > 0) {
debug('job.interval is finite', job);
this.intervals[name] = setInterval(
() => this.run(name),
job.interval
this.intervals.set(
name,
setInterval(() => this.run(name), job.interval)
);
} else {
debug('job.date was scheduled to run only once', job);
}
delete this.timeouts[name];
},
job.timeout,
job.timezone
this.timeouts.delete(name);
}, job.date.getTime() - Date.now())
);

@@ -516,34 +510,77 @@ return;

// This is only complex because both timeout and interval can be a schedule
if (this.isSchedule(job.timeout)) {
debug('job timeout is schedule', job);
this.timeouts.set(
name,
later.setTimeout(
() => {
this.run(name);
if (this.isSchedule(job.interval)) {
debug('job.interval is schedule', job);
this.intervals.set(
name,
later.setInterval(
() => this.run(name),
job.interval,
job.timezone
)
);
} else if (Number.isFinite(job.interval) && job.interval > 0) {
debug('job.interval is finite', job);
this.intervals.set(
name,
setInterval(() => this.run(name), job.interval)
);
}
this.timeouts.delete(name);
},
job.timeout,
job.timezone
)
);
return;
}
if (Number.isFinite(job.timeout)) {
debug('job timeout is finite', job);
this.timeouts[name] = setTimeout(() => {
this.run(name);
this.timeouts.set(
name,
setTimeout(() => {
this.run(name);
if (this.isSchedule(job.interval)) {
debug('job.interval is schedule', job);
this.intervals[name] = later.setInterval(
() => this.run(name),
job.interval,
job.timezone
);
} else if (Number.isFinite(job.interval) && job.interval > 0) {
debug('job.interval is finite', job.interval);
this.intervals[name] = setInterval(
() => this.run(name),
job.interval
);
}
if (this.isSchedule(job.interval)) {
debug('job.interval is schedule', job);
this.intervals.set(
name,
later.setInterval(
() => this.run(name),
job.interval,
job.timezone
)
);
} else if (Number.isFinite(job.interval) && job.interval > 0) {
debug('job.interval is finite', job.interval);
this.intervals.set(
name,
setInterval(() => this.run(name), job.interval)
);
}
delete this.timeouts[name];
}, job.timeout);
this.timeouts.delete(name);
}, job.timeout)
);
} else if (this.isSchedule(job.interval)) {
debug('job.interval is schedule', job);
this.intervals[name] = later.setInterval(
() => this.run(name),
job.interval,
job.timezone
this.intervals.set(
name,
later.setInterval(() => this.run(name), job.interval, job.timezone)
);
} else if (Number.isFinite(job.interval) && job.interval > 0) {
debug('job.interval is finite', job);
this.intervals[name] = setInterval(() => this.run(name), job.interval);
this.intervals.set(
name,
setInterval(() => this.run(name), job.interval)
);
}

@@ -564,4 +601,4 @@

if (this.workers[name]) {
this.workers[name].once('message', (message) => {
if (this.workers.has(name)) {
this.workers.get(name).once('message', (message) => {
if (message === 'cancelled') {

@@ -572,6 +609,6 @@ this.config.logger.info(

);
this.workers[name].terminate();
this.workers.get(name).terminate();
}
});
this.workers[name].postMessage('cancel');
this.workers.get(name).postMessage('cancel');
}

@@ -581,3 +618,3 @@

return pWaitFor(() => this.workers[name] === undefined);
return pWaitFor(() => !this.workers.has(name));
}

@@ -589,3 +626,3 @@

return pWaitFor(() => Object.keys(this.workers).length === 0);
return pWaitFor(() => this.workers.size === 0);
}

@@ -649,11 +686,10 @@

removeSafeTimer(type, name) {
if (this[type][name]) {
if (
typeof this[type][name] === 'object' &&
typeof this[type][name].clear === 'function'
) {
this[type][name].clear();
if (this[type].has(name)) {
const timer = this[type].get(name);
if (typeof timer === 'object' && typeof timer.clear === 'function') {
timer.clear();
}
delete this[type][name];
this[type].delete(name);
}

@@ -672,4 +708,4 @@ }

this.config.removeCompleted &&
!this.timeouts.name &&
!this.intervals.name
!this.timeouts.has(name) &&
!this.intervals.has(name)
) {

@@ -676,0 +712,0 @@ this.config.jobs = this.config.jobs.filter((j) => j.name !== name);

@@ -12,6 +12,6 @@ // Definitions by: Taylor Schley <https://github.com/shadowgate15>

closeWorkerAfterMs: Record<string, unknown>;
workers: Record<string, Worker>;
timeouts: Record<string, Timeout>;
intervals: Record<string, Interval>;
closeWorkerAfterMs: Map<string, Timeout>;
workers: Map<string, Worker>;
timeouts: Map<string, Timeout>;
intervals: Map<string, Interval>;

@@ -62,7 +62,7 @@ isSchedule: (value: any) => boolean;

declare namespace Bree {
interface JobOptions {
name?: string;
path?: string | (() => void);
timeout?: number | string | boolean;
interval?: number | string;
interface Job {
name: string;
path: string | (() => void);
timeout: number | string | boolean;
interval: number | string;
date?: Date;

@@ -78,2 +78,4 @@ cron?: string;

type JobOptions = Required<Pick<Job, 'name'>> & Partial<Omit<Job, 'name'>>;
interface BreeConfigs {

@@ -88,3 +90,3 @@ logger: Record<string, unknown>;

timezone: string;
jobs: Array<string | (() => void) | JobOptions>;
jobs: Job[];
hasSeconds: boolean;

@@ -101,3 +103,5 @@ cronValidate: Record<string, unknown>;

type BreeOptions = Partial<BreeConfigs>;
type BreeOptions = Partial<BreeConfigs> & {
jobs?: Array<string | (() => void) | JobOptions>;
};

@@ -104,0 +108,0 @@ type PluginFunc<T = unknown> = (options: T, c: typeof Bree) => void;

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc