@bedrock/core
Advanced tools
Comparing version 5.1.1 to 6.0.0
# `@bedrock/core` ChangeLog | ||
## 6.0.0 - 2022-04-28 | ||
### Changed | ||
- **BREAKING**: A number of changes have been made to `BedrockError` to bring | ||
it more inline with modern JavaScript errors and common practice and | ||
conventions within bedrock applications. Many of these changes are unlikely | ||
to break existing applications, but they could, so they are listed here. | ||
- `toObject` conversion now uses `serialize-error` internally for | ||
representing error stack traces. | ||
- A private or unspecified `BedrockError`, when converted to | ||
an object, now has a type of `OperationError` (instead of | ||
`bedrock.InternalServerError`. Most bedrock applications do not read | ||
or switch off of this value. | ||
- Using an array for the name of a `BedrockError` is disallowed, | ||
it must be a string. This usage is uncommon and unlikely to impact most | ||
applications. | ||
- When `BedrockError` is converted to an object, it includes | ||
both `name` and `type` (as the same value). The use of `name` is preferred, | ||
but `type` is preserved for backwards compatibility. Applications that | ||
strictly disallowed additional properties on errors will need to allow the | ||
`name` property to be present, but the `name` property is built into | ||
JavaScript errors and allowing for additional properties in errors is | ||
common practice such that this change is unlikely to affect many | ||
applications. The `type` property may be removed in the future, so | ||
applications should normalize to using `name`. | ||
- `isType`, `hasType`, `hasCauseOfType` have been removed. Use the nullish | ||
coalescing operator and compare against `name` instead. Subtle mistakes | ||
can also be made by writing code that is not specific to the location that | ||
particular errors occur in a causal chain, so these functions have been | ||
removed to avoid those problems. These functions are already rarely used in | ||
modern bedrock modules and applications. | ||
- **BREAKING**: Top-level variables used in computed config templates must use | ||
valid JavaScript variable names, e.g., they cannot include hyphens (`-`) or | ||
periods (`.`). A future version may remove compute config templates entirely; | ||
functions should be used instead for computed configs. | ||
- **BREAKING**: `config.paths.cache` and `config.paths.log` are now listed as | ||
requiring overriding in deployments. This replaces the custom code that would | ||
log a warning if they were not set. | ||
- **BREAKING**: Use `commander@9`. An important change is that | ||
`bedrock.program` no longer has properties for command line options, those | ||
are accessed via `bedrock.program.opts()` instead. | ||
See: https://github.com/tj/commander.js/blob/master/CHANGELOG.md | ||
- Calling `bedrock.start` more than once will cause an error to be thrown. This | ||
was already not supported and caused non-deterministic broken behavior, this | ||
change just makes it explicit and deterministic (the same clear error is | ||
always thrown). | ||
- Replace error serializer/deserializer `errio` with `serialize-error`. Should | ||
not be a breaking change. | ||
### Removed | ||
- **BREAKING**: Remove winston mail transport. The winston mail transport which | ||
is disabled by default and not expected to be used in any applications using | ||
5.x has been removed. | ||
- **BREAKING**: Remove `bedrock.util.callbackify`. Import from node.js `util` | ||
if needed. | ||
- **BREAKING**: Remove `bedrock.util.hasValue`. | ||
- **BREAKING**: Remove `bedrock.util.delay`. Import from npm `delay` package | ||
if needed. | ||
- **BREAKING**: Remove `bedrock.util.uuid`. Import from npm `uuid` package | ||
if needed. | ||
- **BREAKING**: Remove `bedrock.util.w3cDate`. | ||
- **BREAKING**: Remove `bedrock.util.isObject`. | ||
- **BREAKING**: Remove `bedrock.util.clone`. Import from npm `klona` package | ||
if needed. | ||
- **BREAKING**: Remove `bedrock.util.boolify`. | ||
- **BREAKING**: Remove `runOnceAsync`. Use `runOnce` instead. | ||
## 5.1.1 - 2022-04-13 | ||
@@ -4,0 +71,0 @@ |
/*! | ||
* Copyright (c) 2012-2021 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
import {fileURLToPath} from 'node:url'; | ||
import path from 'node:path'; | ||
export const config = {}; | ||
// set `__dirname` constant | ||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
// cli info | ||
@@ -50,6 +56,4 @@ config.cli = {}; | ||
config.paths = { | ||
// note: defaults configured in bedrock.js to fail. | ||
// applications MUST set these if used | ||
cache: null, | ||
log: null | ||
cache: path.join(__dirname, '..', '.cache'), | ||
log: path.join('/tmp/bedrock-dev') | ||
}; | ||
@@ -61,3 +65,7 @@ | ||
// an array of path strings (e.g. 'mongodb.host', 'session-mongodb.ttl') | ||
config.ensureConfigOverride.fields = []; | ||
config.ensureConfigOverride.fields = [ | ||
// deployments MUST override cache and log paths | ||
'paths.cache', | ||
'paths.log' | ||
]; | ||
@@ -135,17 +143,6 @@ /* logging options | ||
// transport for email logging | ||
config.loggers.email = {}; | ||
config.loggers.email.level = 'critical'; | ||
config.loggers.email.to = ['cluster@localhost']; | ||
config.loggers.email.from = 'cluster@localhost'; | ||
config.loggers.email.silent = true; | ||
config.loggers.email.json = true; | ||
config.loggers.email.timestamp = true; | ||
// bedrock options | ||
config.loggers.email.bedrock = {}; | ||
// categories-transports map | ||
config.loggers.categories = { | ||
app: ['console', 'app', 'error', 'email'], | ||
app: ['console', 'app', 'error'], | ||
access: ['access', 'error'] | ||
}; |
@@ -5,7 +5,6 @@ /*! | ||
import {config as brConfig} from './config.js'; | ||
import lodashGet from 'lodash.get'; | ||
import lodashSet from 'lodash.set'; | ||
import lodashTemplate from 'lodash.template'; | ||
import lodashToPath from 'lodash.topath'; | ||
import {getByPath, setByPath, toPath} from './helpers.js'; | ||
const VAR_REGEX = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; | ||
/** | ||
@@ -20,158 +19,161 @@ * Wrapper with helpers for config objects. | ||
*/ | ||
export const Config = function(object, options = {}) { | ||
this.object = object; | ||
this.options = options; | ||
}; | ||
export class Config { | ||
constructor(object, options = {}) { | ||
this.object = object; | ||
this.options = options; | ||
} | ||
/** | ||
* Set a path to a value. | ||
* | ||
* Multiple paths can be set at once with an object with many string path keys | ||
* and associated values. | ||
* | ||
* @param {string} path - A lodash-style string or array path, or an object | ||
* with many path key and value pairs. | ||
* @param {*} value - The value to set at the path when using single path. | ||
*/ | ||
Config.prototype.set = function(path, value) { | ||
if(!_isPath(path)) { | ||
Object.keys(path).forEach(key => lodashSet(this.object, key, path[key])); | ||
return; | ||
/** | ||
* Set a path to a value. | ||
* | ||
* Multiple paths can be set at once with an object with many string path | ||
* keys and associated values. | ||
* | ||
* @param {string} path - A lodash-style string or array path, or an object | ||
* with many path key and value pairs. | ||
* @param {*} value - The value to set at the path when using single path. | ||
*/ | ||
set(path, value) { | ||
if(!_isPath(path)) { | ||
Object.keys(path).forEach(key => setByPath(this.object, key, path[key])); | ||
return; | ||
} | ||
setByPath(this.object, path, value); | ||
} | ||
lodashSet(this.object, path, value); | ||
}; | ||
/** | ||
* Set a path to a default value if it does not exist. All elements of the path | ||
* will be initialized as an empty object if they do not exist. | ||
* | ||
* Multiple paths can be set at once with an object with many string path keys | ||
* and associated default values. | ||
* | ||
* Note: To initialize the final element of a path to the empty object even if | ||
* it already exists, use `c.set(path, {})`. | ||
* | ||
* @param {string} path - A lodash-style string or array path, or an object | ||
* with many path key and default value pairs. | ||
* @param {*} value - The default value to set at the path when using a single | ||
* path. | ||
* | ||
* @returns {*} The last element of the path or a path indexed object with | ||
* element values. | ||
*/ | ||
Config.prototype.setDefault = function(path, value) { | ||
if(!_isPath(path)) { | ||
const paths = {}; | ||
Object.keys(path).forEach(key => { | ||
paths[key] = _setDefault(this.object, key, path[key]); | ||
/** | ||
* Set a path to a default value if it does not exist. All elements of the | ||
* path will be initialized as an empty object if they do not exist. | ||
* | ||
* Multiple paths can be set at once with an object with many string path keys | ||
* and associated default values. | ||
* | ||
* Note: To initialize the final element of a path to the empty object even if | ||
* it already exists, use `c.set(path, {})`. | ||
* | ||
* @param {string} path - A lodash-style string or array path, or an object | ||
* with many path key and default value pairs. | ||
* @param {*} value - The default value to set at the path when using a single | ||
* path. | ||
* | ||
* @returns {*} The last element of the path or a path indexed object with | ||
* element values. | ||
*/ | ||
setDefault(path, value) { | ||
if(!_isPath(path)) { | ||
const paths = {}; | ||
Object.keys(path).forEach(key => { | ||
paths[key] = _setDefault(this.object, key, path[key]); | ||
}); | ||
return paths; | ||
} | ||
return _setDefault(this.object, path, value); | ||
} | ||
/** | ||
* Assigns a getter to a config path. When the config path is read, the getter | ||
* will execute and compute the configured value. This is particularly useful | ||
* for config values that depend on other config values; it removes the need | ||
* to update such a value when its dependencies change. | ||
* | ||
* The value can be computed from a function or from a lodash template that | ||
* will be evaluated using `bedrock.config` for its local variables. | ||
* | ||
* @param {string} path - A lodash-style string or array path, or an object | ||
* with many path key and value pairs. | ||
* @param {Function|string} fnOrExpression - A lodash template or a function | ||
* used to compute the path value. | ||
* @param {object} [options] - The options to use: | ||
* locals: object containing variables used for string templates | ||
* parentDefault: value for parent if it does not exist. | ||
* | ||
* @returns {*} The result. | ||
*/ | ||
setComputed(path, fnOrExpression, options) { | ||
if(!_isPath(path)) { | ||
options = fnOrExpression; | ||
Object.keys(path).forEach(key => this.setComputed( | ||
key, path[key], options)); | ||
return; | ||
} | ||
if(typeof fnOrExpression === 'string') { | ||
// handle strings as templates | ||
fnOrExpression = _createTemplateFn(fnOrExpression); | ||
} else if(typeof fnOrExpression !== 'function') { | ||
// handle non-string non-functions as simple values | ||
return this.set(path, fnOrExpression); | ||
} | ||
// ensure path is array | ||
if(typeof path === 'string') { | ||
path = toPath(path); | ||
} | ||
// locals | ||
options = options || {}; | ||
const locals = options.locals || this.options.locals || brConfig; | ||
// get target object path | ||
const targetPath = path.slice(0, -1); | ||
// get key | ||
const targetKey = path.slice(-1); | ||
// get or create target parent object | ||
const parentDefault = options.parentDefault || {}; | ||
const target = _setDefault(this.object, targetPath, parentDefault); | ||
// setup property | ||
let _isSet = false; | ||
let _value; | ||
Object.defineProperty(target, targetKey, { | ||
configurable: true, | ||
enumerable: true, | ||
get() { | ||
if(_isSet) { | ||
return _value; | ||
} | ||
fnOrExpression._target = target; | ||
fnOrExpression._targetKey = targetKey; | ||
return fnOrExpression(locals); | ||
}, | ||
set(value) { | ||
_isSet = true; | ||
_value = value; | ||
} | ||
}); | ||
return paths; | ||
} | ||
return _setDefault(this.object, path, value); | ||
}; | ||
/** | ||
* Assigns a getter to a config path. When the config path is read, the getter | ||
* will execute and compute the configured value. This is particularly useful | ||
* for config values that depend on other config values; it removes the need | ||
* to update such a value when its dependencies change. | ||
* | ||
* The value can be computed from a function or from a lodash template that | ||
* will be evaluated using `bedrock.config` for its local variables. | ||
* | ||
* @param {string} path - A lodash-style string or array path, or an object | ||
* with many path key and value pairs. | ||
* @param {Function|string} fnOrExpression - A lodash template or a function | ||
* used to compute the path value. | ||
* @param {object} [options] - The options to use: | ||
* locals: object containing variables used for string templates | ||
* parentDefault: value for parent if it does not exist. | ||
* | ||
* @returns {*} The result. | ||
*/ | ||
Config.prototype.setComputed = function(path, fnOrExpression, options) { | ||
if(!_isPath(path)) { | ||
options = fnOrExpression; | ||
Object.keys(path).forEach(key => this.setComputed( | ||
key, path[key], options)); | ||
return; | ||
/** | ||
* Create a bound setComputed function for this Config instance. Used to | ||
* simplify code. Example: | ||
* | ||
* let cc = bedrock.util.config.main.computer(); | ||
* cc('...', ...); | ||
* . | ||
* | ||
* @returns {Function} The bound `setComputed` function. | ||
*/ | ||
computer() { | ||
return this.setComputed.bind(this); | ||
} | ||
if(typeof fnOrExpression === 'string') { | ||
// handle strings as templates | ||
fnOrExpression = lodashTemplate(fnOrExpression); | ||
} else if(typeof fnOrExpression !== 'function') { | ||
// handle non-string non-functions as simple values | ||
return this.set(path, fnOrExpression); | ||
/** | ||
* Push a getter to an array specified by a config path. See `setComputed` | ||
* for an explaination of how getters work. | ||
* | ||
* @param {string} path - A lodash-style string or array path. | ||
* @param {Function|string} fnOrExpression - A lodash template or a function | ||
* used to compute the path value. | ||
* @param {object} [options] - The options to use: | ||
* locals: object containing variables used for string templates. | ||
*/ | ||
pushComputed(path, fnOrExpression, options) { | ||
// get target or default array | ||
const target = getByPath(this.object, path, []); | ||
// add next index | ||
const pushPath = toPath(path); | ||
pushPath.push(target.length); | ||
// use default parent array | ||
const pushOptions = {...options, parentDefault: []}; | ||
// set computed array element | ||
this.setComputed(pushPath, fnOrExpression, pushOptions); | ||
} | ||
// ensure path is array | ||
if(typeof path === 'string') { | ||
path = lodashToPath(path); | ||
} | ||
// locals | ||
options = options || {}; | ||
const locals = options.locals || this.options.locals || brConfig; | ||
// get target object path | ||
const targetPath = path.slice(0, -1); | ||
// get key | ||
const targetKey = path.slice(-1); | ||
// get or create target parent object | ||
const parentDefault = options.parentDefault || {}; | ||
const target = _setDefault(this.object, targetPath, parentDefault); | ||
// setup property | ||
let _isSet = false; | ||
let _value; | ||
Object.defineProperty(target, targetKey, { | ||
configurable: true, | ||
enumerable: true, | ||
get: () => { | ||
if(_isSet) { | ||
return _value; | ||
} | ||
return fnOrExpression(locals); | ||
}, | ||
set: value => { | ||
_isSet = true; | ||
_value = value; | ||
} | ||
}); | ||
}; | ||
} | ||
/** | ||
* Create a bound setComputed function for this Config instance. Used to | ||
* simplify code. Example: | ||
* | ||
* let cc = bedrock.util.config.main.computer(); | ||
* cc('...', ...); | ||
* . | ||
* | ||
* @returns {Function} The bound `setComputed` function. | ||
*/ | ||
Config.prototype.computer = function() { | ||
return this.setComputed.bind(this); | ||
}; | ||
/** | ||
* Push a getter to an array specified by a config path. See setComputed for an | ||
* explaination of how getters work. | ||
* | ||
* @param {string} path - A lodash-style string or array path. | ||
* @param {Function|string} fnOrExpression - A lodash template or a function | ||
* used to compute the path value. | ||
* @param {object} [options] - The options to use: | ||
* locals: object containing variables used for string templates. | ||
*/ | ||
Config.prototype.pushComputed = function( | ||
path, fnOrExpression, options) { | ||
// get target or default array | ||
const target = lodashGet(this.object, path, []); | ||
// add next index | ||
const pushPath = lodashToPath(path); | ||
pushPath.push(target.length); | ||
// use default parent array | ||
const pushOptions = Object.assign({}, options, {parentDefault: []}); | ||
// set computed array element | ||
this.setComputed(pushPath, fnOrExpression, pushOptions); | ||
}; | ||
/** | ||
* Shared wrapper for the standard bedrock config. | ||
@@ -190,14 +192,32 @@ */ | ||
if(typeof path === 'string') { | ||
path = lodashToPath(path); | ||
path = toPath(path); | ||
} | ||
if(path.length) { | ||
let target = lodashGet(object, path); | ||
let target = getByPath(object, path); | ||
if(!target) { | ||
target = value; | ||
lodashSet(object, path, target); | ||
setByPath(object, path, target); | ||
} | ||
return target; | ||
} else { | ||
return object; | ||
} | ||
return object; | ||
} | ||
// replacement for lodash.template | ||
function _createTemplateFn(template) { | ||
let running = false; | ||
return function interpolate(vars) { | ||
// prevent cycles by returning `undefined` while `interpolate` is running | ||
if(running) { | ||
return; | ||
} | ||
running = true; | ||
// do not allow any invalid named vars | ||
const names = Object.keys(vars).filter(n => VAR_REGEX.test(n)); | ||
const values = names.map(n => vars[n]); | ||
const fn = new Function(...names, `return \`${template}\`;`); | ||
const result = fn.call(template, ...values); | ||
running = false; | ||
return result; | ||
}; | ||
} |
216
lib/index.js
/*! | ||
* Copyright (c) 2012-2021 Digital Bazaar, Inc. All rights reserved. | ||
* Copyright (c) 2012-2022 Digital Bazaar, Inc. All rights reserved. | ||
*/ | ||
import * as brUtil from './util.js'; | ||
import cluster from 'cluster'; | ||
import {boolify, getByPath} from './helpers.js'; | ||
import cluster from 'node:cluster'; | ||
import {config} from './config.js'; | ||
import cycle from 'cycle'; | ||
import {deprecate} from 'util'; | ||
import {container as loggers} from './loggers/index.js'; | ||
import {cpus} from 'node:os'; | ||
import {emitter as events} from './events.js'; | ||
import errio from 'errio'; | ||
import {fileURLToPath} from 'url'; | ||
import fs from 'fs'; | ||
import lodashGet from 'lodash.get'; | ||
import {container as loggers} from './loggers/index.js'; | ||
import {cpus} from 'os'; | ||
import path from 'path'; | ||
import program from 'commander'; | ||
import {fileURLToPath} from 'node:url'; | ||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import {program} from 'commander'; | ||
import {serializeError, deserializeError} from 'serialize-error'; | ||
// add primary aliases as needed | ||
if(cluster.isPrimary === undefined) { | ||
cluster.isPrimary = cluster.isMaster; | ||
} | ||
if(cluster.setupPrimary === undefined) { | ||
cluster.setupPrimary = cluster.setupMaster; | ||
} | ||
const {BedrockError} = brUtil; | ||
const cc = brUtil.config.main.computer(); | ||
// set `__dirname` constant | ||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
@@ -36,7 +40,3 @@ | ||
if(!script.endsWith('.js')) { | ||
if(script.endsWith('/')) { | ||
script += 'index.js'; | ||
} else { | ||
script += '.js'; | ||
} | ||
script += script.endsWith('/') ? 'index.js' : '.js'; | ||
} | ||
@@ -65,46 +65,4 @@ | ||
let BEDROCK_STARTED = false; | ||
let BEDROCK_START_CALLED = false; | ||
// register error class | ||
errio.register(BedrockError); | ||
// config paths | ||
// configured here instead of config.js due to util dependency issues | ||
// FIXME: v2.0.0: remove when removing warnings below. | ||
// see: https://github.com/digitalbazaar/bedrock/issues/93 | ||
const _warningShown = { | ||
cache: false, | ||
log: false | ||
}; | ||
cc({ | ||
'paths.cache': () => { | ||
// FIXME: v2.0.0: remove warning and default and throw exception . | ||
// see: https://github.com/digitalbazaar/bedrock/issues/93 | ||
//throw new BedrockError( | ||
// 'bedrock.config.paths.cache not set.', | ||
// 'ConfigError'); | ||
const cachePath = path.join(__dirname, '..', '.cache'); | ||
if(!_warningShown.cache) { | ||
loggers.get('app').error( | ||
`"bedrock.config.paths.cache" not set, using default: "${cachePath}"`); | ||
_warningShown.cache = true; | ||
} | ||
return cachePath; | ||
}, | ||
'paths.log': () => { | ||
// FIXME: v2.0.0: remove warning and default and throw exception . | ||
// see: https://github.com/digitalbazaar/bedrock/issues/93 | ||
//throw new BedrockError( | ||
// 'bedrock.config.paths.log not set.', | ||
// 'ConfigError'); | ||
const logPath = path.join('/tmp/bedrock-dev'); | ||
if(!_warningShown.log) { | ||
// Using console since this config value used during logger setup. | ||
console.warn('WARNING: ' + | ||
`"bedrock.config.paths.log" not set, using default: "${logPath}"`); | ||
_warningShown.log = true; | ||
} | ||
return logPath; | ||
} | ||
}); | ||
// expose bedrock program | ||
@@ -125,2 +83,7 @@ const _program = program.version(version); | ||
export async function start(options = {}) { | ||
if(BEDROCK_START_CALLED) { | ||
throw new Error('"start" must not be called more than once.'); | ||
} | ||
BEDROCK_START_CALLED = true; | ||
const startTime = Date.now(); | ||
@@ -141,5 +104,5 @@ | ||
.option('--log-timestamps <timestamps>', | ||
'Override console log timestamps config. (boolean)', brUtil.boolify) | ||
'Override console log timestamps config. (boolean)', boolify) | ||
.option('--log-colorize <colorize>', | ||
'Override console log colorization config. (boolean)', brUtil.boolify) | ||
'Override console log colorization config. (boolean)', boolify) | ||
.option('--log-exclude <modules>', | ||
@@ -173,3 +136,3 @@ 'Do not log events from the specified comma separated modules.') | ||
// run | ||
if(cluster.isMaster) { | ||
if(cluster.isPrimary) { | ||
_runPrimary(startTime, options); | ||
@@ -233,3 +196,3 @@ // don't emit `bedrock.error` in primary process | ||
if(msg.error) { | ||
throw errio.fromObject(msg.error, {stack: true}); | ||
throw deserializeError(msg.error); | ||
} | ||
@@ -246,3 +209,3 @@ return; | ||
error = e; | ||
msg.error = cycle.decycle(errio.toObject(e, {stack: true})); | ||
msg.error = e instanceof BedrockError ? e.toObject() : serializeError(e); | ||
} | ||
@@ -259,8 +222,2 @@ | ||
/** | ||
* **DEPRECATED**: runOnceAsync() is deprecated. Use runOnce() instead. | ||
*/ | ||
export const runOnceAsync = deprecate( | ||
runOnce, 'runOnceAsync() is deprecated. Use runOnce() instead.'); | ||
/** | ||
* Called from a worker to exit gracefully and without an error code. Typically | ||
@@ -299,3 +256,4 @@ * used by subcommands. Use `process.exit(1)` (or other error code) to exit | ||
async function _loadConfigs() { | ||
for(const cfg of program.config) { | ||
const configs = program.opts().config; | ||
for(const cfg of configs) { | ||
await import(path.resolve(process.cwd(), cfg)); | ||
@@ -306,22 +264,24 @@ } | ||
function _configureLoggers() { | ||
const opts = program.opts(); | ||
// set console log flags | ||
if('logLevel' in program) { | ||
config.loggers.console.level = program.logLevel; | ||
if('logLevel' in opts) { | ||
config.loggers.console.level = opts.logLevel; | ||
} | ||
if('logColorize' in program) { | ||
config.loggers.console.colorize = program.logColorize; | ||
if('logColorize' in opts) { | ||
config.loggers.console.colorize = opts.logColorize; | ||
} | ||
if('logTimestamps' in program) { | ||
config.loggers.console.timestamp = program.logTimestamps; | ||
if('logTimestamps' in opts) { | ||
config.loggers.console.timestamp = opts.logTimestamps; | ||
} | ||
if('logExclude' in program) { | ||
if('logExclude' in opts) { | ||
config.loggers.console.bedrock.excludeModules = | ||
program.logExclude.split(','); | ||
opts.logExclude.split(','); | ||
} | ||
if('logOnly' in program) { | ||
config.loggers.console.bedrock.onlyModules = program.logOnly.split(','); | ||
if('logOnly' in opts) { | ||
config.loggers.console.bedrock.onlyModules = opts.logOnly.split(','); | ||
} | ||
// adjust transports | ||
if('logTransports' in program) { | ||
const t = program.logTransports; | ||
if('logTransports' in opts) { | ||
const t = opts.logTransports; | ||
const cats = t.split(','); | ||
@@ -360,3 +320,3 @@ cats.forEach(function(cat) { | ||
} | ||
if(program.silent || program.logLevel === 'none') { | ||
if(opts.silent || opts.logLevel === 'none') { | ||
config.loggers.console.silent = true; | ||
@@ -367,4 +327,5 @@ } | ||
function _configureWorkers() { | ||
if('workers' in program) { | ||
config.core.workers = program.workers; | ||
const opts = program.opts(); | ||
if('workers' in opts) { | ||
config.core.workers = opts.workers; | ||
} | ||
@@ -380,6 +341,4 @@ if(config.core.workers <= 0) { | ||
if(cluster.isMaster) { | ||
cluster.setupMaster({ | ||
exec: path.join(__dirname, 'worker.js') | ||
}); | ||
if(cluster.isPrimary) { | ||
cluster.setupPrimary({exec: path.join(__dirname, 'worker.js')}); | ||
@@ -405,3 +364,3 @@ // set group before initializing loggers | ||
function _setupUncaughtExceptionHandler(logger, logPrefix) { | ||
function _setupUncaughtExceptionHandler(logger) { | ||
// log uncaught exception and exit, except in test mode | ||
@@ -411,3 +370,3 @@ if(config.cli.command.name() !== 'test') { | ||
process.removeAllListeners('uncaughtException'); | ||
logger.critical(`${logPrefix} uncaught error`, {error}); | ||
logger.critical('uncaught error', {error}); | ||
await _exit(1); | ||
@@ -418,3 +377,3 @@ }); | ||
function _setupUnhandledRejectionHandler(logger, logPrefix) { | ||
function _setupUnhandledRejectionHandler(logger) { | ||
// log uncaught exception and exit, except in test mode | ||
@@ -424,3 +383,3 @@ if(config.cli.command.name() !== 'test') { | ||
process.removeAllListeners('unhandledRejection'); | ||
logger.critical(`${logPrefix} unhandled promise rejection`, {error}); | ||
logger.critical('unhandled promise rejection', {error}); | ||
await _exit(1); | ||
@@ -431,3 +390,3 @@ }); | ||
function _setupSignalHandler(logger, logPrefix) { | ||
function _setupSignalHandler(logger) { | ||
const SIGNALS = { | ||
@@ -456,3 +415,3 @@ /* | ||
process.on(signal, async function exitProcess() { | ||
logger.info(`${logPrefix} received signal.`, {signal}); | ||
logger.info('received signal.', {signal}); | ||
await _exit(); | ||
@@ -464,6 +423,3 @@ }); | ||
function _runPrimary(startTime, options) { | ||
// FIXME: use child logger | ||
// see: https://github.com/digitalbazaar/bedrock/issues/90 | ||
const logger = loggers.get('app'); | ||
const logPrefix = '[bedrock/primary]'; | ||
const logger = loggers.get('app').child('bedrock/primary'); | ||
@@ -473,3 +429,3 @@ // setup cluster if running with istanbul coverage | ||
// re-call cover with no reporting and using pid named output | ||
cluster.setupMaster({ | ||
cluster.setupPrimary({ | ||
exec: './node_modules/.bin/istanbul', | ||
@@ -484,15 +440,10 @@ args: [ | ||
const args = process.argv.slice(2).join(' '); | ||
// TODO: Remove all use of config.core.master in next major release (5.x) | ||
// see: https://github.com/digitalbazaar/bedrock/issues/89 | ||
const processTitle = config.core.master ? | ||
config.core.master.title : config.core.primary.title; | ||
const processTitle = config.core.primary.title; | ||
process.title = processTitle + (args ? (' ' + args) : ''); | ||
_setupUncaughtExceptionHandler(logger, logPrefix); | ||
_setupUnhandledRejectionHandler(logger, logPrefix); | ||
_setupSignalHandler(logger, logPrefix); | ||
_setupUncaughtExceptionHandler(logger); | ||
_setupUnhandledRejectionHandler(logger); | ||
_setupSignalHandler(logger); | ||
logger.info( | ||
`${logPrefix} starting process "${processTitle}"`, | ||
{pid: process.pid}); | ||
logger.info(`starting process "${processTitle}"`, {pid: process.pid}); | ||
@@ -521,3 +472,3 @@ // get starting script | ||
logger.info( | ||
`${logPrefix} worker "${worker.process.pid}" exited on purpose ` + | ||
`worker "${worker.process.pid}" exited on purpose ` + | ||
`with code "${code}" and signal "${signal}"; exiting primary process.`); | ||
@@ -527,3 +478,3 @@ } else { | ||
logger.critical( | ||
`${logPrefix} worker "${worker.process.pid}" exited with code ` + | ||
`worker "${worker.process.pid}" exited with code ` + | ||
`"${code}" and signal "${signal}".`); | ||
@@ -565,10 +516,7 @@ } | ||
} | ||
logger.info(`${logPrefix} started`, {timeMs: Date.now() - startTime}); | ||
logger.info('started', {timeMs: Date.now() - startTime}); | ||
} | ||
async function _runWorker(startTime) { | ||
// FIXME: use child logger | ||
// https://github.com/digitalbazaar/bedrock/issues/90 | ||
const logger = loggers.get('app'); | ||
const logPrefix = '[bedrock/worker]'; | ||
const logger = loggers.get('app').child('bedrock/worker'); | ||
@@ -579,7 +527,7 @@ // set 'ps' title | ||
_setupUncaughtExceptionHandler(logger, logPrefix); | ||
_setupUnhandledRejectionHandler(logger, logPrefix); | ||
_setupSignalHandler(logger, logPrefix); | ||
_setupUncaughtExceptionHandler(logger); | ||
_setupUnhandledRejectionHandler(logger); | ||
_setupSignalHandler(logger); | ||
logger.info(`${logPrefix} starting process "${config.core.worker.title}"`); | ||
logger.info(`starting process "${config.core.worker.title}"`); | ||
@@ -620,3 +568,3 @@ const cliReady = await events.emit('bedrock-cli.ready'); | ||
await events.emit('bedrock.started'); | ||
logger.info(`${logPrefix} started`, {timeMs: Date.now() - startTime}); | ||
logger.info('started', {timeMs: Date.now() - startTime}); | ||
} | ||
@@ -738,3 +686,3 @@ | ||
for(const [key, value] of configOverrideSnapshot) { | ||
if(lodashGet(config, key) === value) { | ||
if(getByPath(config, key) === value) { | ||
const error = new Error( | ||
@@ -754,3 +702,3 @@ `The config field "${key}" must be changed during the ` + | ||
for(const field of fields) { | ||
snapshot.set(field, lodashGet(config, field)); | ||
snapshot.set(field, getByPath(config, field)); | ||
} | ||
@@ -808,3 +756,3 @@ return snapshot; | ||
try { | ||
if(cluster.isMaster) { | ||
if(cluster.isPrimary) { | ||
await _preparePrimaryExit(); | ||
@@ -834,12 +782,10 @@ await PRIMARY_STATE.workersExited; | ||
async function _logExit(code = 0) { | ||
if(!cluster.isMaster) { | ||
if(!cluster.isPrimary) { | ||
return; | ||
} | ||
// log final message and wait for logger to flush | ||
const logger = loggers.get('app'); | ||
const logPrefix = '[bedrock/primary]'; | ||
const logger = loggers.get('app').child('bedrock/primary'); | ||
try { | ||
const p = new Promise(resolve => { | ||
logger.info( | ||
`${logPrefix} primary process exiting with code "${code}".`, {code}); | ||
logger.info(`primary process exiting with code "${code}".`, {code}); | ||
logger.once('finish', () => resolve()); | ||
@@ -846,0 +792,0 @@ logger.once('error', () => resolve()); |
@@ -7,7 +7,10 @@ /*! | ||
import {config} from '../config.js'; | ||
import path from 'path'; | ||
import {promises as fs} from 'fs'; | ||
import path from 'node:path'; | ||
import {promises as fs} from 'node:fs'; | ||
import {promisify} from 'node:util'; | ||
import uidNumber from 'uid-number'; | ||
import winston from 'winston'; | ||
const cc = brUtil.config.main.computer(); | ||
const getUserId = promisify(uidNumber); | ||
@@ -40,8 +43,6 @@ // config filenames | ||
const logger = config.loggers[name]; | ||
return (brUtil.isObject(logger) && 'filename' in logger); | ||
return (_isObject(logger) && 'filename' in logger); | ||
}).map(function(name) { | ||
return config.loggers[name]; | ||
}); | ||
// TODO: run in parallel | ||
// see: https://github.com/digitalbazaar/bedrock/issues/88 | ||
for(const fileLogger of fileLoggers) { | ||
@@ -68,33 +69,35 @@ const dirname = path.dirname(fileLogger.filename); | ||
async function _chown(filename) { | ||
if(config.core.running.userId) { | ||
let uid = config.core.running.userId; | ||
if(typeof uid !== 'number') { | ||
if(process.platform === 'win32') { | ||
// on Windows, just get the current UID | ||
uid = process.getuid(); | ||
} else { | ||
try { | ||
let gid; | ||
/* eslint-disable-next-line no-unused-vars */ | ||
[uid, gid] = await new Promise((resolve, reject) => { | ||
uidNumber(uid, (err, uid, gid) => { | ||
if(err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve([uid, gid]); | ||
}); | ||
}); | ||
} catch(e) { | ||
throw new brUtil.BedrockError( | ||
`Unable to convert user "${uid}" to a numeric user id. ` + | ||
'Try using a uid number instead.', | ||
'Error', {cause: e}); | ||
} | ||
let uid = config.core.running.userId; | ||
if(!(uid && process.getgid)) { | ||
return; | ||
} | ||
if(typeof uid !== 'number') { | ||
if(process.platform === 'win32') { | ||
// on Windows, just get the current UID | ||
uid = process.getuid(); | ||
} else { | ||
try { | ||
uid = await getUserId(uid); | ||
} catch(e) { | ||
throw new brUtil.BedrockError( | ||
`Unable to convert user "${uid}" to a numeric user id. ` + | ||
'Try using a uid number instead.', | ||
'Error', {cause: e}); | ||
} | ||
} | ||
if(process.getgid) { | ||
await fs.chown(filename, uid, process.getgid()); | ||
} | ||
} | ||
await fs.chown(filename, uid, process.getgid()); | ||
} | ||
/** | ||
* Returns true if the given value is an Object. | ||
* | ||
* @param {*} value - The value to check. | ||
* | ||
* @returns {boolean} True if it is an Object, false if not. | ||
*/ | ||
function _isObject(value) { | ||
return (Object.prototype.toString.call(value) === '[object Object]'); | ||
} |
@@ -6,10 +6,14 @@ /*! | ||
import * as formatters from './formatters.js'; | ||
import cluster from 'cluster'; | ||
import cluster from 'node:cluster'; | ||
import {config} from '../config.js'; | ||
import crypto from 'crypto'; | ||
import {Mail as WinstonMail} from 'winston-mail'; | ||
import util from 'util'; | ||
import crypto from 'node:crypto'; | ||
import util from 'node:util'; | ||
import winston from 'winston'; | ||
import {WorkerTransport} from './WorkerTransport.js'; | ||
// add primary aliases as needed | ||
if(cluster.isPrimary === undefined) { | ||
cluster.isPrimary = cluster.isMaster; | ||
} | ||
const randomBytes = util.promisify(crypto.randomBytes); | ||
@@ -68,4 +72,11 @@ | ||
} | ||
// create child logger from wrapper (merging child meta into parent meta) | ||
return createChild.apply(wrapper, [childMeta]); | ||
const child = createChild.apply(wrapper, [childMeta]); | ||
// if child `defaultMeta` is not set, add it to ensure that the parent | ||
// meta is included | ||
if(!Object.hasOwnProperty(child, 'defaultMeta')) { | ||
child.defaultMeta = {...wrapper.defaultMeta, ...childMeta}; | ||
} | ||
return child; | ||
}; | ||
@@ -78,3 +89,3 @@ logger = wrapper; | ||
if(cluster.isMaster) { | ||
if(cluster.isPrimary) { | ||
// reserved transports | ||
@@ -85,3 +96,2 @@ container.transports = { | ||
console: null, | ||
email: null, | ||
error: null, | ||
@@ -97,3 +107,3 @@ }; | ||
if(cluster.isMaster) { | ||
if(cluster.isPrimary) { | ||
// create shared transports | ||
@@ -105,3 +115,2 @@ const transports = container.transports; | ||
}); | ||
transports.email = new WinstonMail(config.loggers.email); | ||
@@ -186,3 +195,3 @@ if(config.loggers.enableFileTransport) { | ||
container.addTransport = function(name, transport) { | ||
if(!cluster.isMaster) { | ||
if(!cluster.isPrimary) { | ||
return; | ||
@@ -189,0 +198,0 @@ } |
348
lib/util.js
@@ -5,5 +5,4 @@ /*! | ||
import {config} from './config.js'; | ||
import delay from 'delay'; | ||
import util from 'util'; | ||
import uuid from 'uuid-random'; | ||
import {serializeError} from 'serialize-error'; | ||
import util from 'node:util'; | ||
@@ -13,305 +12,92 @@ // export config utilities under `config` namespace | ||
/** | ||
* Create a promise which resolves after the specified milliseconds. | ||
* | ||
* @see {@link https://github.com/sindresorhus/delay} | ||
*/ | ||
export {delay}; | ||
// BedrockError class | ||
export const BedrockError = function(message, type, details, cause) { | ||
export const BedrockError = function( | ||
message = 'An unspecified error occurred.', ...args) { | ||
Error.call(this, message); | ||
Error.captureStackTrace(this, this.constructor); | ||
this.name = type; | ||
let options; | ||
if(args[0]) { | ||
// legacy unnamed parameters | ||
if(typeof args[0] !== 'object') { | ||
options = { | ||
name: args[0], | ||
details: args[1], | ||
cause: args[2] | ||
}; | ||
} else { | ||
// named parameters | ||
options = args[0]; | ||
} | ||
} else { | ||
options = {}; | ||
} | ||
if(options.name && typeof options.name !== 'string') { | ||
throw new TypeError('"name" must be a string.'); | ||
} | ||
this.name = options.name ?? 'OperationError'; | ||
this.message = message; | ||
this.details = details || null; | ||
this.cause = cause || null; | ||
this.details = options.details ?? null; | ||
this.cause = options.cause ?? null; | ||
}; | ||
util.inherits(BedrockError, Error); | ||
BedrockError.prototype.name = 'BedrockError'; | ||
BedrockError.prototype.toObject = function(options) { | ||
options = options || {}; | ||
options.public = options.public || false; | ||
BedrockError.prototype.toObject = function(options = {}) { | ||
options.public = options.public ?? false; | ||
// convert error to object | ||
const rval = _toObject(this, options); | ||
return _toObject(this, options); | ||
}; | ||
// add stack trace only for non-public development conversion | ||
if(!options.public && config.core.errors.showStack) { | ||
// try a basic parse | ||
rval.stack = _parseStack(this.stack); | ||
} | ||
return rval; | ||
const _genericErrorJSON = { | ||
message: 'An unspecified error occurred.', | ||
name: 'OperationError', | ||
type: 'OperationError' | ||
}; | ||
// check type of this error | ||
BedrockError.prototype.isType = function(type) { | ||
return hasValue(this, 'name', type); | ||
}; | ||
// check type of this error or one of it's causes | ||
BedrockError.prototype.hasType = function(type) { | ||
return this.isType(type) || this.hasCauseOfType(type); | ||
}; | ||
// check type of error cause or one of it's causes | ||
BedrockError.prototype.hasCauseOfType = function(type) { | ||
if(this.cause && this.cause instanceof BedrockError) { | ||
return this.cause.hasType(type); | ||
} | ||
return false; | ||
}; | ||
/** | ||
* Gets the passed date in W3C format (eg: 2011-03-09T21:55:41Z). | ||
* | ||
* @param {Date|string|number} [date=new Date] - The date; if passing a number | ||
* use milliseconds since the epoch. | ||
* | ||
* @returns {string} The date in W3C format. | ||
*/ | ||
export function w3cDate(date) { | ||
if(date === undefined || date === null) { | ||
date = new Date(); | ||
} else if(typeof date === 'number' || typeof date === 'string') { | ||
date = new Date(date); | ||
} | ||
return util.format('%d-%s-%sT%s:%s:%sZ', | ||
date.getUTCFullYear(), | ||
_zeroFill(date.getUTCMonth() + 1), | ||
_zeroFill(date.getUTCDate()), | ||
_zeroFill(date.getUTCHours()), | ||
_zeroFill(date.getUTCMinutes()), | ||
_zeroFill(date.getUTCSeconds())); | ||
} | ||
function _toObject(err, options, visited = new Set()) { | ||
visited.add(err); | ||
function _zeroFill(num) { | ||
return (num < 10) ? '0' + num : '' + num; | ||
} | ||
/** | ||
* Merges the contents of one or more objects into the first object. | ||
* | ||
* Arguments: | ||
* `deep` (optional), true to do a deep-merge | ||
* `target` the target object to merge properties into | ||
* `objects` N objects to merge into the target. | ||
* | ||
* @returns {object} - The extended object. | ||
*/ | ||
export function extend() { | ||
let deep = false; | ||
let i = 0; | ||
if(arguments.length > 0 && typeof arguments[0] === 'boolean') { | ||
deep = arguments[0]; | ||
++i; | ||
// if conversion is for public consumption but the error itself is not | ||
// public, then return a generic error | ||
if(options.public && !_isErrorPublic(err)) { | ||
return _genericErrorJSON; | ||
} | ||
const target = arguments[i] || {}; | ||
i++; | ||
for(; i < arguments.length; ++i) { | ||
const obj = arguments[i] || {}; | ||
Object.keys(obj).forEach(function(name) { | ||
const value = obj[name]; | ||
if(deep && isObject(value) && !Array.isArray(value)) { | ||
target[name] = extend(true, target[name], value); | ||
} else { | ||
target[name] = value; | ||
} | ||
}); | ||
} | ||
return target; | ||
} | ||
/** | ||
* Returns true if the given value is an Object. | ||
* | ||
* @param {*} value - The value to check. | ||
* | ||
* @returns {boolean} True if it is an Object, false if not. | ||
*/ | ||
export function isObject(value) { | ||
return (Object.prototype.toString.call(value) === '[object Object]'); | ||
} | ||
// convert the top-level error | ||
const object = serializeError(err); | ||
/** | ||
* Clones a value. If the value is an array or an object it will be deep | ||
* cloned. | ||
* | ||
* @param {*} value - The value to clone. | ||
* | ||
* @returns {*} The clone. | ||
*/ | ||
export function clone(value) { | ||
if(value && typeof value === 'object') { | ||
let rval; | ||
if(Array.isArray(value)) { | ||
rval = new Array(value.length); | ||
for(let i = 0; i < rval.length; i++) { | ||
rval[i] = clone(value[i]); | ||
} | ||
} else { | ||
rval = {}; | ||
for(const j in value) { | ||
rval[j] = clone(value[j]); | ||
} | ||
} | ||
return rval; | ||
} | ||
return value; | ||
} | ||
// convert for public consumption (remove any private details) | ||
if(options.public) { | ||
// delete `public` property in details | ||
delete object?.details.public; | ||
/** | ||
* Generates a new v4 UUID. | ||
* | ||
* @returns {string} The new v4 UUID. | ||
*/ | ||
export {uuid}; | ||
/** | ||
* Parse the string or value and return a boolean value or raise an exception. | ||
* Handles true and false booleans and case-insensitive 'yes', 'no', 'true', | ||
* 'false', 't', 'f', '0', '1' strings. | ||
* | ||
* @param {string} value - The value to convert to a boolean. | ||
* | ||
* @returns {boolean} The boolean conversion of the value. | ||
*/ | ||
export function boolify(value) { | ||
if(typeof value === 'boolean') { | ||
return value; | ||
} | ||
if(typeof value === 'string' && value) { | ||
switch(value.toLowerCase()) { | ||
case 'true': | ||
case 't': | ||
case '1': | ||
case 'yes': | ||
case 'y': | ||
return true; | ||
case 'false': | ||
case 'f': | ||
case '0': | ||
case 'no': | ||
case 'n': | ||
return false; | ||
// delete stack trace unless configured otherwise | ||
if(!config.core.errors.showStack) { | ||
delete object.stack; | ||
} | ||
} | ||
// if here we couldn't parse it | ||
throw new Error('Invalid boolean:' + value); | ||
} | ||
export function callbackify(fn) { | ||
const callbackVersion = util.callbackify(fn); | ||
return function(...args) { | ||
const callback = args[args.length - 1]; | ||
if(typeof callback === 'function') { | ||
return callbackVersion.apply(null, args); | ||
// if the cause is not public, clear it | ||
if(err.cause && !_isErrorPublic(err.cause)) { | ||
object.cause = null; | ||
} | ||
return fn.apply(null, args); | ||
}; | ||
} | ||
// a replacement for jsonld.hasValue | ||
export function hasValue(obj, key, value) { | ||
const t = obj[key]; | ||
if(Array.isArray(t)) { | ||
return t.includes(value); | ||
} | ||
return t === value; | ||
} | ||
const _genericErrorJSON = { | ||
message: 'An internal server error occurred.', | ||
type: 'bedrock.InternalServerError' | ||
}; | ||
const _errorMessageRegex = /^Error:\s*/; | ||
const _errorAtRegex = /^\s+at\s*/; | ||
/** | ||
* Parse an Error stack property into an object structure that can be | ||
* serialized to JSON. | ||
* | ||
* NOTE: Uses some format heuristics and may be fooled by tricky errors. | ||
* | ||
* TODO: look into better stack parsing libraries. | ||
* See: https://github.com/digitalbazaar/bedrock/issues/87. | ||
* | ||
* @param {string} stack - The stack trace. | ||
* | ||
* @returns {object} Stack trace as an object. | ||
*/ | ||
function _parseStack(stack) { | ||
try { | ||
const lines = stack.split('\n'); | ||
const messageLines = []; | ||
const atLines = []; | ||
for(let i = 0; i < lines.length; ++i) { | ||
const line = lines[i]; | ||
// push location-like lines to a stack array | ||
if(line.match(_errorAtRegex)) { | ||
atLines.push(line.replace(_errorAtRegex, '')); | ||
} else { | ||
// push everything else to a message array | ||
messageLines.push(line.replace(_errorMessageRegex, '')); | ||
} | ||
} | ||
return { | ||
message: messageLines.join('\n'), | ||
at: atLines | ||
}; | ||
} catch(e) { | ||
// FIXME: add parse error handling | ||
// see: https://github.com/digitalbazaar/bedrock/issues/87 | ||
return stack; | ||
// convert any cause to an object (instead of a string) if not yet | ||
// visited (cycle detection) | ||
if(err.cause && !visited.has(err.cause)) { | ||
object.cause = _toObject(err.cause, options, visited); | ||
} | ||
} | ||
function _toObject(err, options) { | ||
if(!err) { | ||
return null; | ||
// include `type` as `name` for better backwards compatibility | ||
if(!object.type) { | ||
object.type = object.name; | ||
} | ||
if(options.public) { | ||
// public conversion | ||
// FIXME also check if a validation type? | ||
if(err instanceof BedrockError && | ||
err.details && err.details.public) { | ||
const details = clone(err.details); | ||
delete details.public; | ||
// mask cause if it is not a public bedrock error | ||
let {cause} = err; | ||
if(!(cause && cause instanceof BedrockError && | ||
cause.details && cause.details.public)) { | ||
cause = null; | ||
} | ||
return { | ||
message: err.message, | ||
type: err.name, | ||
details, | ||
cause: _toObject(cause, options) | ||
}; | ||
} else { | ||
// non-bedrock error or not public, return generic error | ||
return _genericErrorJSON; | ||
} | ||
} else { | ||
// full private conversion | ||
if(err instanceof BedrockError) { | ||
return { | ||
message: err.message, | ||
type: err.name, | ||
details: err.details, | ||
cause: _toObject(err.cause, options) | ||
}; | ||
} else { | ||
return { | ||
message: err.message, | ||
type: err.name, | ||
details: { | ||
inspect: util.inspect(err, false, 10), | ||
stack: _parseStack(err.stack) | ||
}, | ||
cause: null | ||
}; | ||
} | ||
} | ||
return object; | ||
} | ||
function _isErrorPublic(err) { | ||
return err instanceof BedrockError && err?.details?.public; | ||
} |
{ | ||
"name": "@bedrock/core", | ||
"version": "5.1.1", | ||
"version": "6.0.0", | ||
"type": "module", | ||
@@ -29,16 +29,9 @@ "description": "A core foundation for rich Web applications.", | ||
"@digitalbazaar/async-node-events": "^3.0.0", | ||
"commander": "^2.20.3", | ||
"commander": "^9.2.0", | ||
"cycle": "^1.0.3", | ||
"delay": "^5.0.0", | ||
"errio": "^1.2.2", | ||
"fast-safe-stringify": "^2.1.1", | ||
"lodash.get": "^4.4.2", | ||
"lodash.set": "^4.3.2", | ||
"lodash.template": "^4.5.0", | ||
"lodash.topath": "^4.5.2", | ||
"serialize-error": "^10.0.0", | ||
"triple-beam": "^1.3.0", | ||
"uid-number": "0.0.6", | ||
"uuid-random": "^1.3.2", | ||
"uid-number": "^0.0.6", | ||
"winston": "^3.7.2", | ||
"winston-mail": "^2.0.0", | ||
"winston-transport": "^4.5.0" | ||
@@ -45,0 +38,0 @@ }, |
@@ -741,4 +741,4 @@ <img src="https://digitalbazaar.com/wp-content/uploads/BedrockLogo.png"> | ||
* Linux, Mac OS X, Windows | ||
* node.js >= 0.10.x | ||
* npm >= 1.4.x | ||
* node.js >= 14.x | ||
* npm >= 6.x | ||
@@ -822,9 +822,18 @@ Running Bedrock | ||
[bedrock-account]: https://github.com/digitalbazaar/bedrock-account | ||
[bedrock-account-http]: https://github.com/digitalbazaar/bedrock-account-http | ||
[bedrock-authn-token]: https://github.com/digitalbazaar/bedrock-authn-token | ||
[bedrock-app-identity]: https://github.com/digitalbazaar/bedrock-app-identity | ||
[bedrock-edv-storage]: https://github.com/digitalbazaar/bedrock-edv-storage | ||
[bedrock-express]: https://github.com/digitalbazaar/bedrock-express | ||
[bedrock-jobs]: https://github.com/digitalbazaar/bedrock-jobs | ||
[bedrock-kms]: https://github.com/digitalbazaar/bedrock-kms | ||
[bedrock-kms-http]: https://github.com/digitalbazaar/bedrock-kms-http | ||
[bedrock-meter]: https://github.com/digitalbazaar/bedrock-meter | ||
[bedrock-mongodb]: https://github.com/digitalbazaar/bedrock-mongodb | ||
[bedrock-quasar]: https://github.com/digitalbazaar/bedrock-quasar | ||
[bedrock-server]: https://github.com/digitalbazaar/bedrock-server | ||
[bedrock-validation]: https://github.com/digitalbazaar/bedrock-validation | ||
[bedrock-views]: https://github.com/digitalbazaar/bedrock-views | ||
[bedrock-vue]: https://github.com/digitalbazaar/bedrock-vue | ||
[bedrock-web]: https://github.com/digitalbazaar/bedrock-web | ||
[bedrock-webpack]: https://github.com/digitalbazaar/bedrock-webpack | ||
[winston]: https://github.com/winstonjs/winston |
@@ -8,53 +8,92 @@ /*! | ||
describe('bedrock', function() { | ||
describe('util.extend()', function() { | ||
it('should perform in-place default extension', function(done) { | ||
const result = {}; | ||
bedrock.util.extend(result, {a: 1}); | ||
result.should.eql({a: 1}); | ||
done(); | ||
describe('util.BedrockError', function() { | ||
it('should have correct name using legacy params', function() { | ||
const err = new BedrockError('E', 'NAME', null, null); | ||
err.name.should.equal('NAME'); | ||
err.message.should.equal('E'); | ||
should.equal(err.cause, null); | ||
should.equal(err.details, null); | ||
}); | ||
it('should perform in-place deep extension', function(done) { | ||
const result = {a: {a0: 0}, b: 2}; | ||
bedrock.util.extend(true, result, {a: {a1: 1}}); | ||
result.should.eql({a: {a0: 0, a1: 1}, b: 2}); | ||
done(); | ||
it('should have correct attributes using legacy params', function() { | ||
const err0 = new BedrockError('E0', 'E0TYPE', null, null); | ||
const err1 = new BedrockError('E1', 'E1TYPE', {foo: 'bar'}, err0); | ||
err0.name.should.equal('E0TYPE'); | ||
err0.message.should.equal('E0'); | ||
should.equal(err0.cause, null); | ||
should.equal(err0.details, null); | ||
err1.name.should.equal('E1TYPE'); | ||
err1.message.should.equal('E1'); | ||
err1.cause.should.equal(err0); | ||
err1.details.should.eql({foo: 'bar'}); | ||
}); | ||
it('should perform in-place shallow extension', function(done) { | ||
const result = {a: {a0: 0}, b: 2}; | ||
bedrock.util.extend(false, result, {a: {a1: 1}}); | ||
result.should.eql({a: {a1: 1}, b: 2}); | ||
done(); | ||
it('should have correct name using named params', function() { | ||
const err = new BedrockError('E', 'NAME', null, null); | ||
err.name.should.equal('NAME'); | ||
err.message.should.equal('E'); | ||
should.equal(err.cause, null); | ||
should.equal(err.details, null); | ||
}); | ||
it('should be able to return a new object', function(done) { | ||
const result = bedrock.util.extend(true, {}, {a: 1}); | ||
result.should.eql({a: 1}); | ||
done(); | ||
it('should have correct attributes using named params', function() { | ||
const err0 = new BedrockError('E0', 'E0TYPE', null, null); | ||
const err1 = new BedrockError('E1', 'E1TYPE', {foo: 'bar'}, err0); | ||
err0.name.should.equal('E0TYPE'); | ||
err0.message.should.equal('E0'); | ||
should.equal(err0.cause, null); | ||
should.equal(err0.details, null); | ||
err1.name.should.equal('E1TYPE'); | ||
err1.message.should.equal('E1'); | ||
err1.cause.should.equal(err0); | ||
err1.details.should.eql({foo: 'bar'}); | ||
}); | ||
it('should merge multiple objects into a new object', function(done) { | ||
const result = {}; | ||
bedrock.util.extend(true, result, {a: 1}, {b: 2}); | ||
result.should.eql({a: 1, b: 2}); | ||
done(); | ||
it('should have private info', function() { | ||
const err = new BedrockError('Error message.'); | ||
const object = err.toObject(); | ||
object.should.include.keys(['name', 'type', 'message']); | ||
object.name.should.equal('OperationError'); | ||
object.type.should.equal('OperationError'); | ||
object.message.should.equal('Error message.'); | ||
}); | ||
}); | ||
describe('util.BedrockError', function() { | ||
it('should have correct type', function(done) { | ||
const err = new BedrockError('E', 'TYPE', null, null); | ||
err.isType('BOGUS').should.be.false; | ||
err.isType('TYPE').should.be.true; | ||
err.hasType('BOGUS').should.be.false; | ||
err.hasType('TYPE').should.be.true; | ||
done(); | ||
it('should have only public info', function() { | ||
const err = new BedrockError('Error message.'); | ||
const object = err.toObject({public: true}); | ||
object.should.include.keys(['name', 'type', 'message']); | ||
object.name.should.equal('OperationError'); | ||
object.type.should.equal('OperationError'); | ||
object.message.should.equal('An unspecified error occurred.'); | ||
}); | ||
it('should have correct cause', function(done) { | ||
const err0 = new BedrockError('E0', 'E0TYPE', null, null); | ||
const err1 = new BedrockError('E1', 'E1TYPE', null, err0); | ||
err1.isType('BOGUS').should.be.false; | ||
err1.isType('E1TYPE').should.be.true; | ||
err1.hasType('BOGUS').should.be.false; | ||
err1.hasType('E0TYPE').should.be.true; | ||
err1.hasType('E1TYPE').should.be.true; | ||
done(); | ||
it('should have explicit public info with legacy params', function() { | ||
const err = new BedrockError('Error message.', 'OperationError', { | ||
public: true | ||
}, new BedrockError('cause', 'DataError', {public: true})); | ||
const object = err.toObject({public: true}); | ||
object.should.include.keys(['type', 'message']); | ||
object.name.should.equal('OperationError'); | ||
object.type.should.equal('OperationError'); | ||
object.message.should.equal('Error message.'); | ||
object.details.should.eql({}); | ||
should.exist(object.cause); | ||
object.cause.message.should.equal('cause'); | ||
object.cause.name.should.equal('DataError'); | ||
object.cause.type.should.equal('DataError'); | ||
}); | ||
it('should have explicit public info with named params', function() { | ||
const err = new BedrockError('Error message.', { | ||
name: 'OperationError', | ||
details: {public: true}, | ||
cause: new BedrockError('cause', { | ||
name: 'DataError', | ||
details: {public: true} | ||
}) | ||
}); | ||
const object = err.toObject({public: true}); | ||
object.should.include.keys(['type', 'message']); | ||
object.name.should.equal('OperationError'); | ||
object.type.should.equal('OperationError'); | ||
object.message.should.equal('Error message.'); | ||
object.details.should.eql({}); | ||
should.exist(object.cause); | ||
object.cause.message.should.equal('cause'); | ||
object.cause.name.should.equal('DataError'); | ||
object.cause.type.should.equal('DataError'); | ||
}); | ||
}); | ||
@@ -61,0 +100,0 @@ |
@@ -5,4 +5,4 @@ /*! | ||
import {config} from '@bedrock/core'; | ||
import {fileURLToPath} from 'url'; | ||
import path from 'path'; | ||
import {fileURLToPath} from 'node:url'; | ||
import path from 'node:path'; | ||
@@ -9,0 +9,0 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
162633
9
27
838
1
2318
1
+ Addedserialize-error@^10.0.0
+ Addedcommander@9.5.0(transitive)
+ Addedserialize-error@10.0.0(transitive)
+ Addedtype-fest@2.19.0(transitive)
- Removeddelay@^5.0.0
- Removederrio@^1.2.2
- Removedlodash.get@^4.4.2
- Removedlodash.set@^4.3.2
- Removedlodash.template@^4.5.0
- Removedlodash.topath@^4.5.2
- Removeduuid-random@^1.3.2
- Removedwinston-mail@^2.0.0
- Removedaddressparser@0.3.2(transitive)
- Removedcommander@2.20.3(transitive)
- Removeddelay@5.0.0(transitive)
- Removedemailjs@2.2.0(transitive)
- Removedemailjs-base64@1.1.4(transitive)
- Removedemailjs-mime-codec@2.0.9(transitive)
- Removederrio@1.2.2(transitive)
- Removedlodash._reinterpolate@3.0.0(transitive)
- Removedlodash.get@4.4.2(transitive)
- Removedlodash.set@4.3.2(transitive)
- Removedlodash.template@4.5.0(transitive)
- Removedlodash.templatesettings@4.2.0(transitive)
- Removedlodash.topath@4.5.2(transitive)
- Removedmustache@2.3.2(transitive)
- Removedramda@0.26.1(transitive)
- Removedtext-encoding@0.7.0(transitive)
- Removeduuid-random@1.3.2(transitive)
- Removedwinston-mail@2.0.0(transitive)
Updatedcommander@^9.2.0
Updateduid-number@^0.0.6