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

@bedrock/core

Package Overview
Dependencies
Maintainers
5
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bedrock/core - npm Package Compare versions

Comparing version 5.1.1 to 6.0.0

lib/helpers.js

67

CHANGELOG.md
# `@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 @@

31

lib/config.js
/*!
* 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;
};
}
/*!
* 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 @@ }

@@ -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

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