Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

bree

Package Overview
Dependencies
Maintainers
1
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 1.1.23 to 1.1.24

615

index.js

@@ -17,3 +17,2 @@ const EventEmitter = require('events');

class Bree extends EventEmitter {
// eslint-disable-next-line complexity
constructor(config) {

@@ -94,2 +93,3 @@ super();

this.validateJob = this.validateJob.bind(this);
this.getWorkerMetadata = this.getWorkerMetadata.bind(this);

@@ -99,2 +99,4 @@ this.run = this.run.bind(this);

this.stop = this.stop.bind(this);
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);

@@ -161,337 +163,353 @@ // validate root (sync check)

// support a simple string which we will transform to have a path
if (isSANB(job)) {
// throw an error if duplicate job names
if (names.includes(job))
errors.push(
new Error(`Job #${i + 1} has a duplicate job name of ${job}`)
);
else names.push(job);
this.validateJob(job, i, names, errors);
}
if (!this.config.root) {
errors.push(
new Error(
`Job #${
i + 1
} "${job}" requires root directory option to auto-populate path`
)
);
continue;
}
debug('this.config.jobs', this.config.jobs);
const path = join(
this.config.root,
job.endsWith('.js') || job.endsWith('.mjs')
? job
: `${job}.${this.config.defaultExtension}`
// if there were any errors then throw them
if (errors.length > 0) throw combineErrors(errors);
}
// eslint-disable-next-line complexity
validateJob(job, i, names, errors) {
// support a simple string which we will transform to have a path
if (isSANB(job)) {
// don't allow a job to have the `index` file name
if (['index', 'index.js', 'index.mjs'].includes(job)) {
errors.push(
new Error(
'You cannot use the reserved job name of "index", "index.js", nor "index.mjs"'
)
);
try {
const stats = statSync(path);
if (!stats.isFile())
throw new Error(`Job #${i + 1} "${job}" path missing: ${path}`);
this.config.jobs[i] = {
name: job,
path,
timeout: this.config.timeout,
interval: this.config.interval
};
} catch (err) {
errors.push(err);
}
continue;
} else if (typeof job === 'function') {
// TODO check for anonymous function and error
// throw an error if duplicate job names
if (names.includes(job.name))
errors.push(
new Error(`Job #${i + 1} has a duplicate job name of ${job}`)
);
else names.push(job.name);
return { names, errors };
}
const path = `(${job.toString()})()`;
// can't be a built-in or bound function
if (path.includes('[native code]'))
errors.push(
new Error(`Job #${i + 1} can't be a bound or built-in function`)
);
// throw an error if duplicate job names
if (names.includes(job))
errors.push(
new Error(`Job #${i + 1} has a duplicate job name of ${job}`)
);
else names.push(job);
if (!this.config.root) {
errors.push(
new Error(
`Job #${
i + 1
} "${job}" requires root directory option to auto-populate path`
)
);
return { names, errors };
}
const path = join(
this.config.root,
job.endsWith('.js') || job.endsWith('.mjs')
? job
: `${job}.${this.config.defaultExtension}`
);
try {
const stats = statSync(path);
if (!stats.isFile())
throw new Error(`Job #${i + 1} "${job}" path missing: ${path}`);
this.config.jobs[i] = {
name: job.name,
name: job,
path,
worker: { eval: true },
timeout: this.config.timeout,
interval: this.config.interval
};
continue;
} catch (err) {
errors.push(err);
}
// must be a pure object
if (typeof job !== 'object' || Array.isArray(job)) {
errors.push(new Error(`Job #${i + 1} must be an Object`));
continue;
}
return { names, errors };
}
// validate name
if (!isSANB(job.name)) {
errors.push(new Error(`Job #${i + 1} must have a non-empty name`));
delete job.name;
}
// use a prefix for errors
const prefix = `Job #${i + 1} named "${job.name || ''}"`;
if (typeof job.path === 'function') {
const path = `(${job.path.toString()})()`;
// can't be a built-in or bound function
if (path.includes('[native code]'))
errors.push(
new Error(`Job #${i + 1} can't be a bound or built-in function`)
);
this.config.jobs[i].path = path;
this.config.jobs[i].worker = {
eval: true,
...job.worker
};
} else if (!isSANB(job.path) && !this.config.root) {
// job is a function
if (typeof job === 'function') {
// throw an error if duplicate job names
if (names.includes(job.name))
errors.push(
new Error(
`${prefix} requires root directory option to auto-populate path`
)
new Error(`Job #${i + 1} has a duplicate job name of ${job}`)
);
} else {
// validate path
const path = isSANB(job.path)
? job.path
: job.name
? join(
this.config.root,
job.name.endsWith('.js') || job.name.endsWith('.mjs')
? job.name
: `${job.name}.${this.config.defaultExtension}`
)
: false;
if (path) {
try {
const stats = statSync(path);
if (!stats.isFile())
throw new Error(`${prefix} path missing: ${path}`);
if (!isSANB(job.path)) this.config.jobs[i].path = path;
} catch (err) {
errors.push(err);
}
} else {
errors.push(new Error(`${prefix} path missing`));
}
}
else names.push(job.name);
// don't allow users to mix interval AND cron
if (
typeof job.interval !== 'undefined' &&
typeof job.cron !== 'undefined'
) {
const path = `(${job.toString()})()`;
// can't be a built-in or bound function
if (path.includes('[native code]'))
errors.push(
new Error(
`${prefix} cannot have both interval and cron configuration`
)
new Error(`Job #${i + 1} can't be a bound or built-in function`)
);
}
// don't allow users to mix timeout AND date
if (typeof job.timeout !== 'undefined' && typeof job.date !== 'undefined')
errors.push(new Error(`${prefix} cannot have both timeout and date`));
this.config.jobs[i] = {
name: job.name,
path,
worker: { eval: true },
timeout: this.config.timeout,
interval: this.config.interval
};
// throw an error if duplicate job names
if (job.name && names.includes(job.name))
return { names, errors };
}
// must be a pure object
if (typeof job !== 'object' || Array.isArray(job)) {
errors.push(new Error(`Job #${i + 1} must be an Object`));
return { names, errors };
}
// validate name
if (!isSANB(job.name)) {
errors.push(new Error(`Job #${i + 1} must have a non-empty name`));
delete job.name;
}
// use a prefix for errors
const prefix = `Job #${i + 1} named "${job.name || ''}"`;
if (typeof job.path === 'function') {
const path = `(${job.path.toString()})()`;
// can't be a built-in or bound function
if (path.includes('[native code]'))
errors.push(
new Error(`${prefix} has a duplicate job name of ${job.name}`)
new Error(`Job #${i + 1} can't be a bound or built-in function`)
);
else if (job.name) names.push(job.name);
// validate date
if (typeof job.date !== 'undefined' && !(job.date instanceof Date))
errors.push(new Error(`${prefix} had an invalid Date of ${job.date}`));
// validate timeout
if (typeof job.timeout !== 'undefined') {
this.config.jobs[i].path = path;
this.config.jobs[i].worker = {
eval: true,
...job.worker
};
} else if (!isSANB(job.path) && !this.config.root) {
errors.push(
new Error(
`${prefix} requires root directory option to auto-populate path`
)
);
} else {
// validate path
const path = isSANB(job.path)
? job.path
: job.name
? join(
this.config.root,
job.name.endsWith('.js') || job.name.endsWith('.mjs')
? job.name
: `${job.name}.${this.config.defaultExtension}`
)
: false;
if (path) {
try {
this.config.jobs[i].timeout = this.parseValue(job.timeout);
const stats = statSync(path);
if (!stats.isFile())
throw new Error(`${prefix} path missing: ${path}`);
if (!isSANB(job.path)) this.config.jobs[i].path = path;
} catch (err) {
errors.push(
combineErrors([
new Error(`${prefix} had an invalid timeout of ${job.timeout}`),
err
])
);
errors.push(err);
}
} else {
errors.push(new Error(`${prefix} path missing`));
}
}
// validate interval
if (typeof job.interval !== 'undefined') {
try {
this.config.jobs[i].interval = this.parseValue(job.interval);
} catch (err) {
errors.push(
combineErrors([
new Error(`${prefix} had an invalid interval of ${job.interval}`),
err
])
);
}
}
// don't allow users to mix interval AND cron
if (
typeof job.interval !== 'undefined' &&
typeof job.cron !== 'undefined'
) {
errors.push(
new Error(`${prefix} cannot have both interval and cron configuration`)
);
}
// validate hasSeconds
if (
typeof job.hasSeconds !== 'undefined' &&
typeof job.hasSeconds !== 'boolean'
)
// don't allow users to mix timeout AND date
if (typeof job.timeout !== 'undefined' && typeof job.date !== 'undefined')
errors.push(new Error(`${prefix} cannot have both timeout and date`));
// don't allow a job to have the `index` file name
if (['index', 'index.js', 'index.mjs'].includes(job.name)) {
errors.push(
new Error(
'You cannot use the reserved job name of "index", "index.js", nor "index.mjs"'
)
);
return { names, errors };
}
// throw an error if duplicate job names
if (job.name && names.includes(job.name))
errors.push(
new Error(`${prefix} has a duplicate job name of ${job.name}`)
);
else if (job.name) names.push(job.name);
// validate date
if (typeof job.date !== 'undefined' && !(job.date instanceof Date))
errors.push(new Error(`${prefix} had an invalid Date of ${job.date}`));
// validate timeout
if (typeof job.timeout !== 'undefined') {
try {
this.config.jobs[i].timeout = this.parseValue(job.timeout);
} catch (err) {
errors.push(
new Error(
`${prefix} had hasSeconds value of ${job.hasSeconds} (it must be a Boolean)`
)
combineErrors([
new Error(`${prefix} had an invalid timeout of ${job.timeout}`),
err
])
);
}
}
// validate cronValidate
if (
typeof job.cronValidate !== 'undefined' &&
typeof job.cronValidate !== 'object'
)
// validate interval
if (typeof job.interval !== 'undefined') {
try {
this.config.jobs[i].interval = this.parseValue(job.interval);
} catch (err) {
errors.push(
new Error(
`${prefix} had cronValidate value set, but it must be an Object`
)
combineErrors([
new Error(`${prefix} had an invalid interval of ${job.interval}`),
err
])
);
// if `hasSeconds` was `true` then set `cronValidate` and inherit any existing options
if (job.hasSeconds) {
const preset =
job.cronValidate && job.cronValidate.preset
? job.cronValidate.preset
: this.config.cronValidate && this.config.cronValidate.preset
? this.config.cronValidate.preset
: 'default';
const override = {
...(this.config.cronValidate && this.config.cronValidate.override
? this.config.cronValidate.override
: {}),
...(job.cronValidate && job.cronValidate.override
? job.cronValidate.override
: {}),
useSeconds: true
};
this.config.jobs[i].cronValidate = {
...this.config.cronValidate,
...job.cronValidate,
preset,
override
};
}
}
// validate cron
if (typeof job.cron !== 'undefined') {
if (this.isSchedule(job.cron)) {
this.config.jobs[i].interval = job.cron;
// delete this.config.jobs[i].cron;
// validate hasSeconds
if (
typeof job.hasSeconds !== 'undefined' &&
typeof job.hasSeconds !== 'boolean'
)
errors.push(
new Error(
`${prefix} had hasSeconds value of ${job.hasSeconds} (it must be a Boolean)`
)
);
// validate cronValidate
if (
typeof job.cronValidate !== 'undefined' &&
typeof job.cronValidate !== 'object'
)
errors.push(
new Error(
`${prefix} had cronValidate value set, but it must be an Object`
)
);
// if `hasSeconds` was `true` then set `cronValidate` and inherit any existing options
if (job.hasSeconds) {
const preset =
job.cronValidate && job.cronValidate.preset
? job.cronValidate.preset
: this.config.cronValidate && this.config.cronValidate.preset
? this.config.cronValidate.preset
: 'default';
const override = {
...(this.config.cronValidate && this.config.cronValidate.override
? this.config.cronValidate.override
: {}),
...(job.cronValidate && job.cronValidate.override
? job.cronValidate.override
: {}),
useSeconds: true
};
this.config.jobs[i].cronValidate = {
...this.config.cronValidate,
...job.cronValidate,
preset,
override
};
}
// validate cron
if (typeof job.cron !== 'undefined') {
if (this.isSchedule(job.cron)) {
this.config.jobs[i].interval = job.cron;
// delete this.config.jobs[i].cron;
} else {
//
// validate cron pattern
// (must support patterns such as `* * L * *` and `0 0/5 14 * * ?` (and aliases too)
//
// TODO: <https://github.com/Airfooox/cron-validate/issues/67>
//
const result = cron(
job.cron,
typeof job.cronValidate === 'undefined'
? this.config.cronValidate
: job.cronValidate
);
if (result.isValid()) {
const schedule = later.schedule(
later.parse.cron(
job.cron,
boolean(
typeof job.hasSeconds === 'undefined'
? this.config.hasSeconds
: job.hasSeconds
)
)
);
// NOTE: it is always valid
this.config.jobs[i].interval = schedule;
// if (schedule.isValid()) {
// this.config.jobs[i].interval = schedule;
// } // else {
// errors.push(
// new Error(
// `${prefix} had an invalid cron schedule (see <https://crontab.guru> if you need help)`
// )
// );
// }
// above code will never be called
} else {
//
// validate cron pattern
// (must support patterns such as `* * L * *` and `0 0/5 14 * * ?` (and aliases too)
//
// TODO: <https://github.com/Airfooox/cron-validate/issues/67>
//
const result = cron(
job.cron,
typeof job.cronValidate === 'undefined'
? this.config.cronValidate
: job.cronValidate
);
if (result.isValid()) {
const schedule = later.schedule(
later.parse.cron(
job.cron,
boolean(
typeof job.hasSeconds === 'undefined'
? this.config.hasSeconds
: job.hasSeconds
)
)
for (const message of result.getError()) {
errors.push(
new Error(`${prefix} had an invalid cron pattern: ${message}`)
);
// NOTE: it is always valid
this.config.jobs[i].interval = schedule;
// if (schedule.isValid()) {
// this.config.jobs[i].interval = schedule;
// } // else {
// errors.push(
// new Error(
// `${prefix} had an invalid cron schedule (see <https://crontab.guru> if you need help)`
// )
// );
// }
// above code will never be called
} else {
for (const message of result.getError()) {
errors.push(
new Error(`${prefix} had an invalid cron pattern: ${message}`)
);
}
}
}
}
// validate closeWorkerAfterMs
if (
typeof job.closeWorkerAfterMs !== 'undefined' &&
(!Number.isFinite(job.closeWorkerAfterMs) ||
job.closeWorkerAfterMs <= 0)
)
errors.push(
new Error(
`${prefix} had an invalid closeWorkersAfterMs value of ${job.closeWorkersAfterMs} (it must be a finite number > 0)`
)
);
// if timeout was undefined, cron was undefined,
// and date was undefined then set the default
// (as long as the default timeout is >= 0)
if (
Number.isFinite(this.config.timeout) &&
this.config.timeout >= 0 &&
typeof this.config.jobs[i].timeout === 'undefined' &&
typeof job.cron === 'undefined' &&
typeof job.date === 'undefined'
)
this.config.jobs[i].timeout = this.config.timeout;
// if interval was undefined, cron was undefined,
// and date was undefined then set the default
// (as long as the default interval is > 0)
if (
Number.isFinite(this.config.interval) &&
this.config.interval > 0 &&
typeof this.config.jobs[i].interval === 'undefined' &&
typeof job.cron === 'undefined' &&
typeof job.date === 'undefined'
)
this.config.jobs[i].interval = this.config.interval;
}
// don't allow a job to have the `index` file name
// validate closeWorkerAfterMs
if (
names.includes('index') ||
names.includes('index.js') ||
names.includes('index.mjs')
typeof job.closeWorkerAfterMs !== 'undefined' &&
(!Number.isFinite(job.closeWorkerAfterMs) || job.closeWorkerAfterMs <= 0)
)
errors.push(
new Error(
'You cannot use the reserved job name of "index", "index.js", nor "index.mjs"'
`${prefix} had an invalid closeWorkersAfterMs value of ${job.closeWorkersAfterMs} (it must be a finite number > 0)`
)
);
debug('this.config.jobs', this.config.jobs);
// if timeout was undefined, cron was undefined,
// and date was undefined then set the default
// (as long as the default timeout is >= 0)
if (
Number.isFinite(this.config.timeout) &&
this.config.timeout >= 0 &&
typeof this.config.jobs[i].timeout === 'undefined' &&
typeof job.cron === 'undefined' &&
typeof job.date === 'undefined'
)
this.config.jobs[i].timeout = this.config.timeout;
// if there were any errors then throw them
if (errors.length > 0) throw combineErrors(errors);
// if interval was undefined, cron was undefined,
// and date was undefined then set the default
// (as long as the default interval is > 0)
if (
Number.isFinite(this.config.interval) &&
this.config.interval > 0 &&
typeof this.config.jobs[i].interval === 'undefined' &&
typeof job.cron === 'undefined' &&
typeof job.date === 'undefined'
)
this.config.jobs[i].interval = this.config.interval;
return { names, errors };
}

@@ -791,4 +809,33 @@

}
add(jobs) {
//
// validate jobs
//
if (!Array.isArray(jobs)) throw new Error('Jobs must be an Array');
const names = this.config.jobs.map((j) => j.name);
const errors = [];
for (const job of jobs) {
this.validateJob(job, this.config.jobs.length, names, errors);
}
debug('jobs added', this.config.jobs);
// if there were any errors then throw them
if (errors.length > 0) throw combineErrors(errors);
}
remove(name) {
const job = this.config.jobs.find((j) => j.name === name);
if (!job) throw new Error(`Job "${name}" does not exist`);
this.config.jobs = this.config.jobs.find((j) => j.name !== name);
// make sure it also closes any open workers
this.stop(name);
}
}
module.exports = Bree;
{
"name": "bree",
"description": "The best job scheduler for Node.js with support for cron, dates, ms, later, and human-friendly strings. Uses workers to spawn sandboxed processes, and supports async/await, retries, throttling, concurrency, and cancelable promises (graceful shutdown). Simple, fast, and the most lightweight tool for the job. Made for Forward Email and Lad.",
"version": "1.1.23",
"version": "1.1.24",
"author": "Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)",

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

@@ -276,2 +276,9 @@ <h1 align="center">

bree.run('beep');
// add a job after initialization:
bree.add(['boop']);
// this must then be started using one of the above methods
// remove a job after initialization:
bree.remove('boop');
*/

@@ -278,0 +285,0 @@ ```

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