first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+214
View File
@@ -0,0 +1,214 @@
# `cls-hooked` Changelog
## v4.1.7
* fix: npm engine semver to allow node 6.10.x.
* chore: forked async-hook to updated its engine semver also
## v4.1.6
* fix: Use the correct `err` variable name in try/catch. Thanks to [@enko](https://github.com/enko).
## v4.1.5
* dep: update engine support ^4.7||^6.9.2||^7.3 to be same as `async-hook`'s
* dep: update `async-hook` to 1.7.1
* test: give `fs.watchFile` a little more time to pass on Travis CI
## v4.1.4
* feat: supports node 4.5.0 now
* test: add node 4 to travis
## v4.1.3
* dep: updated dependencies. Fix eslint issues
* feat: add runPromise
## v4.1.2
* chore: republishing to npm v4.1.2
* test: Update travis and strict npm engine to ^6.2.2
## v4.1.1
* test: Updated travis and strict npm engine to ^6.2.2
## v4.1.0
* feat: add `runAndReturn` method to get return value of `func` (from [@overlookmotel](https://github.com/overlookmotel/node-continuation-local-storage)).
## v4.0.1
* feat: Same API but major change to implementation. Uses **unofficial** [AsyncWrap](https://github.com/nodejs/node-eps/blob/async-wrap-ep/XXX-asyncwrap-api.md) instead of [async-listener](https://github.com/othiym23/async-listener).
### v3.1.0 (2014-07-28):
* Updated to use `async-listener@0.4.7` to pick up bug fixes.
### v3.0.0 (2013-12-14):
* Removed the notion of a "default" or "global" context per namespace.
It only existed to create a simpler interface for developing and testing the module,
and created the potential for nasty information disclosure bugs
(see [issue #14](https://github.com/othiym23/node-continuation-local-storage/issues/14)
for details). This is potentially a breaking change, if you're depending on the global context,
so semver says we have to bump the major version.
* Added this changelog.
### v2.6.2 (2013-12-07):
* `async-listener` and `emitter-listener` dependency refresh.
### v2.6.1 (2013-11-29):
* `emitter-listener` has been extracted from `shimmer` into a standalone module
for `namespace.bindEmitter()`.
### v2.6.0 (2013-11-27):
* When an error is thrown in a CLS-bound continuation chain, attach the active
context for the namespace to which the chain is bound. This is necessary
because CLS and asyncListeners actually do too good a job of cleaning up
after errors, and so they don't escape the continuation chain. New Relic
needs the context so it can get the transaction active when the error
happened for error tracing.
### v2.5.2 (2013-10-30):
* `async-listener` dependency refresh for better support of node 0.8.0 - 0.8.3.
### v2.5.1 (2013-10-27):
* `async-listener` dependency refresh.
### v2.5.0 (2013-10-27):
* Relax the requirement that CLS contexts be pushed and popped from a stack,
instead treating them as a set. This allows context interleaving (i.e.
using the lower-level `namespace.enter()` and `namespace.exit()` API without
any strict ordering dependencies). Everything works, but this still makes me
a little uneasy.
* EEs can now be bound to multiple namespaces, although this is likely to be
slow.
### v2.4.4 (2013-10-27):
* Even if you use an EE bound to a namespace outside a continuation chain, it
shouldn't explode.
### v2.4.3 (2013-10-16):
* `async-listener` dependency refresh.
### v2.4.2 (2013-10-13):
* More tweaks for `async-listener` error handlers (just a dependency refresh).
### v2.4.1 (2013-10-12):
* `async-listener` error listeners have gotten lots of tweaks. Update to newest
API.
* Only exit namespace context on error if a continuation chain is active.
### v2.4.0 (2013-10-11):
* `async-listener` now supports error listeners. Update to newest API.
* Namespace context should be exited on asynchronous errors.
### v2.3.4 (2013-10-03):
* When EEs are in the middle of emitting, make sure that calls to
`emitter.removeListener` are testing against non-monkeypatched versions of
the event handlers (necessary so certain Connect middleware functions, such
as `connect.limit`, run correctly).
### v2.3.3 (2013-10-02):
* Ensure handler rebinding gets called even in case of errors.
* Be consistent about making sure contexts are kept in a sane state when errors
are thrown in EEs.
### v2.3.2 (2013-10-02):
* Guard `on` / `addListener` remonkeypatching in `namespace.bindEmitter()` so
that `shimmer` is only called to rebind if the monkeypatched versions have
actually been replaced.
* Don't try to call emit if there are no listeners on a bound EE.
* Don't use `setImmediate` in tests, because it's not available in Node 0.8.x.
### v2.3.1 (2013-10-01):
* Update to newest version of `async-listener`.
* Fix typo.
### v2.3.0 (2013-09-30):
* EventEmitters can now be bound to CLS namespaces. Because EEs act as coupling
points between asynchronous domains, it's necessary for the EE binding to
capture the CLS context both when the listener is added, and when a matching
handler is firing because of a matching event being emitted.
### v2.2.1 (2013-09-30):
* More tweaks to conform with `asyncListener` API changes.
* Many more test cases to ensure `asyncListener` stuff is working with Node
0.8.x.
### v2.2.0 (2013-09-26):
* Square up with latest `async-listener` / node PR #6011 changes.
### v2.1.2 (2013-09-09):
* Document `namespace.createContext()`.
* Fix issue where a value was *always* being returned from `namespace.run()`,
even on error.
### v2.1.1 (2013-09-03):
* Clean up minor typo in docs.
### v2.1.0 (2013-09-03):
* Incorporate documentation from failed CLS PR.
* `namespace.bind()` now also always exits the domain, even on error.
* Namespaces can be destroyed.
* `cls.reset()` allows tests to nuke all existing namespaces (use with care
obviously).
### v2.0.0 (2013-09-01):
* Use `async-listener` polyfill instead of `cls-glue`.
* Incorporate tests from `cls-glue`.
### v1.1.1 (2013-09-01):
* Namespace exits context even on error.
### v1.1.0 (2013-07-30):
* Split createContext so it's part of the namespace API.
* Tweak error message to be more informative.
### v1.0.1 (2013-07-25):
* Correct Tim's email address.
### v1.0.0 (2013-07-25):
* Each application of CLS is allocated its own "namespace", which bind data to
continuation chains, either using `.run()` or `.bind()` to create a new
nested context. These nested contexts are prototype chains that point back to
a "default" / "global" context, with the default context for each namespace
being a prototype-free "data bag" created with `Object.create(null)`.
### v0.1.1 (2013-05-03):
* Document progress thus far.
### v0.1.0 (2013-05-03):
* First attempt: basic API, docs, and tests.
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2013-2016, Forrest L Norvell <ogd@aoaioxxysz.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+293
View File
@@ -0,0 +1,293 @@
[![NPM](https://nodei.co/npm/cls-hooked.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/cls-hooked/)
[![Build Status](https://travis-ci.org/Jeff-Lewis/cls-hooked.svg?branch=master)](https://travis-ci.org/Jeff-Lewis/cls-hooked)
# Continuation-Local Storage ( Hooked )
### This is a fork of [CLS](https://github.com/othiym23/node-continuation-local-storage) using [AsyncWrap](https://github.com/nodejs/node-eps/blob/async-wrap-ep/XXX-asyncwrap-api.md) OR [async_hooks](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md) instead of [async-listener](https://github.com/othiym23/async-listener).
### When running Nodejs version < 8, this module uses [AsyncWrap](https://github.com/nodejs/node-eps/blob/async-wrap-ep/XXX-asyncwrap-api.md) which is an unsupported Nodejs API, so please consider the risk before using it.
### When running Nodejs version >= 8.2.1, this module uses the newer [async_hooks](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md) API which is considered `Experimental` by Nodejs.
### Thanks to [@trevnorris](https://github.com/trevnorris) for [AsyncWrap](https://github.com/nodejs/node-eps/blob/async-wrap-ep/XXX-asyncwrap-api.md), [async_hooks](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md) and all the async work in Node and [@AndreasMadsen](https://github.com/AndreasMadsen) for [async-hook](https://github.com/AndreasMadsen/async-hook)
### A little history of "AsyncWrap/async_hooks" and its incarnations
1. First implementation was called **[AsyncListener](https://github.com/nodejs/node-v0.x-archive/pull/6011)** in node v0.11 but was [removed from core](https://github.com/nodejs/node-v0.x-archive/pull/8110) prior to Nodejs v0.12
2. Second implementation called **[AsyncWrap, async-wrap or async_wrap](https://github.com/nodejs/node-eps/blob/async-wrap-ep/XXX-asyncwrap-api.md)** was included to Nodejs v0.12.
- `AsyncWrap` is unofficial and undocumented but is currently in Nodejs versions 6 & 7
- `cls-hooked` uses `AsyncWrap` when run in Node < 8.
3. Third implementation and [offically Node-eps accepted](https://github.com/nodejs/node-eps/blob/master/006-asynchooks-api.md) **AsyncHooks ([async_hooks](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md)) API** was included in Nodejs v8. :)
**The latest version of `cls-hooked` uses [async_hooks](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md) API when run in Node >= 8.2.1**
---
Continuation-local storage works like thread-local storage in threaded
programming, but is based on chains of Node-style callbacks instead of threads.
The standard Node convention of functions calling functions is very similar to
something called ["continuation-passing style"][cps] in functional programming,
and the name comes from the way this module allows you to set and get values
that are scoped to the lifetime of these chains of function calls.
Suppose you're writing a module that fetches a user and adds it to a session
before calling a function passed in by a user to continue execution:
```javascript
// setup.js
var createNamespace = require('cls-hooked').createNamespace;
var session = createNamespace('my session');
var db = require('./lib/db.js');
function start(options, next) {
db.fetchUserById(options.id, function (error, user) {
if (error) return next(error);
session.set('user', user);
next();
});
}
```
Later on in the process of turning that user's data into an HTML page, you call
another function (maybe defined in another module entirely) that wants to fetch
the value you set earlier:
```javascript
// send_response.js
var getNamespace = require('cls-hooked').getNamespace;
var session = getNamespace('my session');
var render = require('./lib/render.js')
function finish(response) {
var user = session.get('user');
render({user: user}).pipe(response);
}
```
When you set values in continuation-local storage, those values are accessible
until all functions called from the original function synchronously or
asynchronously have finished executing. This includes callbacks passed to
`process.nextTick` and the [timer functions][] ([setImmediate][],
[setTimeout][], and [setInterval][]), as well as callbacks passed to
asynchronous functions that call native functions (such as those exported from
the `fs`, `dns`, `zlib` and `crypto` modules).
A simple rule of thumb is anywhere where you might have set a property on the
`request` or `response` objects in an HTTP handler, you can (and should) now
use continuation-local storage. This API is designed to allow you extend the
scope of a variable across a sequence of function calls, but with values
specific to each sequence of calls.
Values are grouped into namespaces, created with `createNamespace()`. Sets of
function calls are grouped together by calling them within the function passed
to `.run()` on the namespace object. Calls to `.run()` can be nested, and each
nested context this creates has its own copy of the set of values from the
parent context. When a function is making multiple asynchronous calls, this
allows each child call to get, set, and pass along its own context without
overwriting the parent's.
A simple, annotated example of how this nesting behaves:
```javascript
var createNamespace = require('cls-hooked').createNamespace;
var writer = createNamespace('writer');
writer.run(function () {
writer.set('value', 0);
requestHandler();
});
function requestHandler() {
writer.run(function(outer) {
// writer.get('value') returns 0
// outer.value is 0
writer.set('value', 1);
// writer.get('value') returns 1
// outer.value is 1
process.nextTick(function() {
// writer.get('value') returns 1
// outer.value is 1
writer.run(function(inner) {
// writer.get('value') returns 1
// outer.value is 1
// inner.value is 1
writer.set('value', 2);
// writer.get('value') returns 2
// outer.value is 1
// inner.value is 2
});
});
});
setTimeout(function() {
// runs with the default context, because nested contexts have ended
console.log(writer.get('value')); // prints 0
}, 1000);
}
```
## cls.createNamespace(name)
* return: {Namespace}
Each application wanting to use continuation-local values should create its own
namespace. Reading from (or, more significantly, writing to) namespaces that
don't belong to you is a faux pas.
## cls.getNamespace(name)
* return: {Namespace}
Look up an existing namespace.
## cls.destroyNamespace(name)
Dispose of an existing namespace. WARNING: be sure to dispose of any references
to destroyed namespaces in your old code, as contexts associated with them will
no longer be propagated.
## cls.reset()
Completely reset all continuation-local storage namespaces. WARNING: while this
will stop the propagation of values in any existing namespaces, if there are
remaining references to those namespaces in code, the associated storage will
still be reachable, even though the associated state is no longer being updated.
Make sure you clean up any references to destroyed namespaces yourself.
## process.namespaces
* return: dictionary of {Namespace} objects
Continuation-local storage has a performance cost, and so it isn't enabled
until the module is loaded for the first time. Once the module is loaded, the
current set of namespaces is available in `process.namespaces`, so library code
that wants to use continuation-local storage only when it's active should test
for the existence of `process.namespaces`.
## Class: Namespace
Application-specific namespaces group values local to the set of functions
whose calls originate from a callback passed to `namespace.run()` or
`namespace.bind()`.
### namespace.active
* return: the currently active context on a namespace
### namespace.set(key, value)
* return: `value`
Set a value on the current continuation context. Must be set within an active
continuation chain started with `namespace.run()` or `namespace.bind()`.
### namespace.get(key)
* return: the requested value, or `undefined`
Look up a value on the current continuation context. Recursively searches from
the innermost to outermost nested continuation context for a value associated
with a given key. Must be set within an active continuation chain started with
`namespace.run()` or `namespace.bind()`.
### namespace.run(callback)
* return: the context associated with that callback
Create a new context on which values can be set or read. Run all the functions
that are called (either directly, or indirectly through asynchronous functions
that take callbacks themselves) from the provided callback within the scope of
that namespace. The new context is passed as an argument to the callback
when it's called.
### namespace.runAndReturn(callback)
* return: the return value of the callback
Create a new context on which values can be set or read. Run all the functions
that are called (either directly, or indirectly through asynchronous functions
that take callbacks themselves) from the provided callback within the scope of
that namespace. The new context is passed as an argument to the callback
when it's called.
Same as `namespace.run()` but returns the return value of the callback rather
than the context.
### namespace.bind(callback, [context])
* return: a callback wrapped up in a context closure
Bind a function to the specified namespace. Works analogously to
`Function.bind()` or `domain.bind()`. If context is omitted, it will default to
the currently active context in the namespace, or create a new context if none
is currently defined.
### namespace.bindEmitter(emitter)
Bind an EventEmitter to a namespace. Operates similarly to `domain.add`, with a
less generic name and the additional caveat that unlike domains, namespaces
never implicitly bind EventEmitters to themselves when they're created within
the context of an active namespace.
The most likely time you'd want to use this is when you're using Express or
Connect and want to make sure your middleware execution plays nice with CLS, or
are doing other things with HTTP listeners:
```javascript
http.createServer(function (req, res) {
writer.bindEmitter(req);
writer.bindEmitter(res);
// do other stuff, some of which is asynchronous
});
```
### namespace.createContext()
* return: a context cloned from the currently active context
Use this with `namespace.bind()`, if you want to have a fresh context at invocation time,
as opposed to binding time:
```javascript
function doSomething(p) {
console.log("%s = %s", p, ns.get(p));
}
function bindLater(callback) {
return writer.bind(callback, writer.createContext());
}
setInterval(function () {
var bound = bindLater(doSomething);
bound('test');
}, 100);
```
## context
A context is a plain object created using the enclosing context as its prototype.
# copyright & license
See [LICENSE](https://github.com/jeff-lewis/cls-hooked/blob/master/LICENSE)
for the details of the BSD 2-clause "simplified" license used by
`continuation-local-storage`. This package was developed in 2012-2013 (and is
maintained now) by Forrest L Norvell, [@othiym23](https://github.com/othiym23),
with considerable help from Timothy Caswell,
[@creationix](https://github.com/creationix), working for The Node Firm. This
work was underwritten by New Relic for use in their Node.js instrumentation
agent, so maybe give that a look if you have some Node.js
performance-monitoring needs.
[timer functions]: https://nodejs.org/api/timers.html
[setImmediate]: https://nodejs.org/api/timers.html#timers_setimmediate_callback_arg
[setTimeout]: https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg
[setInterval]: https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_arg
[cps]: http://en.wikipedia.org/wiki/Continuation-passing_style
+420
View File
@@ -0,0 +1,420 @@
'use strict';
const util = require('util');
const assert = require('assert');
const wrapEmitter = require('emitter-listener');
const asyncHook = require('async-hook-jl');
const CONTEXTS_SYMBOL = 'cls@contexts';
const ERROR_SYMBOL = 'error@context';
//const trace = [];
const invertedProviders = [];
for (let key in asyncHook.providers) {
invertedProviders[asyncHook.providers[key]] = key;
}
const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED;
let currentUid = -1;
module.exports = {
getNamespace: getNamespace,
createNamespace: createNamespace,
destroyNamespace: destroyNamespace,
reset: reset,
//trace: trace,
ERROR_SYMBOL: ERROR_SYMBOL
};
function Namespace(name) {
this.name = name;
// changed in 2.7: no default context
this.active = null;
this._set = [];
this.id = null;
this._contexts = new Map();
}
Namespace.prototype.set = function set(key, value) {
if (!this.active) {
throw new Error('No context available. ns.run() or ns.bind() must be called first.');
}
if (DEBUG_CLS_HOOKED) {
debug2(' SETTING KEY:' + key + '=' + value + ' in ns:' + this.name + ' uid:' + currentUid + ' active:' +
util.inspect(this.active, true));
}
this.active[key] = value;
return value;
};
Namespace.prototype.get = function get(key) {
if (!this.active) {
if (DEBUG_CLS_HOOKED) {
debug2(' GETTING KEY:' + key + '=undefined' + ' ' + this.name + ' uid:' + currentUid + ' active:' +
util.inspect(this.active, true));
}
return undefined;
}
if (DEBUG_CLS_HOOKED) {
debug2(' GETTING KEY:' + key + '=' + this.active[key] + ' ' + this.name + ' uid:' + currentUid + ' active:' +
util.inspect(this.active, true));
}
return this.active[key];
};
Namespace.prototype.createContext = function createContext() {
if (DEBUG_CLS_HOOKED) {
debug2(' CREATING Context: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' + ' active:' +
util.inspect(this.active, true, 2, true));
}
let context = Object.create(this.active ? this.active : Object.prototype);
context._ns_name = this.name;
context.id = currentUid;
if (DEBUG_CLS_HOOKED) {
debug2(' CREATED Context: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' + ' context:' +
util.inspect(context, true, 2, true));
}
return context;
};
Namespace.prototype.run = function run(fn) {
let context = this.createContext();
this.enter(context);
try {
if (DEBUG_CLS_HOOKED) {
debug2(' BEFORE RUN: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' +
util.inspect(context));
}
fn(context);
return context;
}
catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;
}
throw exception;
}
finally {
if (DEBUG_CLS_HOOKED) {
debug2(' AFTER RUN: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' +
util.inspect(context));
}
this.exit(context);
}
};
Namespace.prototype.runAndReturn = function runAndReturn(fn) {
var value;
this.run(function (context) {
value = fn(context);
});
return value;
};
/**
* Uses global Promise and assumes Promise is cls friendly or wrapped already.
* @param {function} fn
* @returns {*}
*/
Namespace.prototype.runPromise = function runPromise(fn) {
let context = this.createContext();
this.enter(context);
let promise = fn(context);
if (!promise || !promise.then || !promise.catch) {
throw new Error('fn must return a promise.');
}
if (DEBUG_CLS_HOOKED) {
debug2(' BEFORE runPromise: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' +
util.inspect(context));
}
return promise
.then(result => {
if (DEBUG_CLS_HOOKED) {
debug2(' AFTER runPromise: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' +
util.inspect(context));
}
this.exit(context);
return result;
})
.catch(err => {
err[ERROR_SYMBOL] = context;
if (DEBUG_CLS_HOOKED) {
debug2(' AFTER runPromise: ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' ' +
util.inspect(context));
}
this.exit(context);
throw err;
});
};
Namespace.prototype.bind = function bindFactory(fn, context) {
if (!context) {
if (!this.active) {
context = this.createContext();
}
else {
context = this.active;
}
}
let self = this;
return function clsBind() {
self.enter(context);
try {
return fn.apply(this, arguments);
}
catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;
}
throw exception;
}
finally {
self.exit(context);
}
};
};
Namespace.prototype.enter = function enter(context) {
assert.ok(context, 'context must be provided for entering');
if (DEBUG_CLS_HOOKED) {
debug2(' ENTER ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' context: ' +
util.inspect(context));
}
this._set.push(this.active);
this.active = context;
};
Namespace.prototype.exit = function exit(context) {
assert.ok(context, 'context must be provided for exiting');
if (DEBUG_CLS_HOOKED) {
debug2(' EXIT ' + this.name + ' uid:' + currentUid + ' len:' + this._set.length + ' context: ' +
util.inspect(context));
}
// Fast path for most exits that are at the top of the stack
if (this.active === context) {
assert.ok(this._set.length, 'can\'t remove top context');
this.active = this._set.pop();
return;
}
// Fast search in the stack using lastIndexOf
let index = this._set.lastIndexOf(context);
if (index < 0) {
if (DEBUG_CLS_HOOKED) {
debug2('??ERROR?? context exiting but not entered - ignoring: ' + util.inspect(context));
}
assert.ok(index >= 0, 'context not currently entered; can\'t exit. \n' + util.inspect(this) + '\n' +
util.inspect(context));
} else {
assert.ok(index, 'can\'t remove top context');
this._set.splice(index, 1);
}
};
Namespace.prototype.bindEmitter = function bindEmitter(emitter) {
assert.ok(emitter.on && emitter.addListener && emitter.emit, 'can only bind real EEs');
let namespace = this;
let thisSymbol = 'context@' + this.name;
// Capture the context active at the time the emitter is bound.
function attach(listener) {
if (!listener) {
return;
}
if (!listener[CONTEXTS_SYMBOL]) {
listener[CONTEXTS_SYMBOL] = Object.create(null);
}
listener[CONTEXTS_SYMBOL][thisSymbol] = {
namespace: namespace,
context: namespace.active
};
}
// At emit time, bind the listener within the correct context.
function bind(unwrapped) {
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) {
return unwrapped;
}
let wrapped = unwrapped;
let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL];
Object.keys(unwrappedContexts).forEach(function (name) {
let thunk = unwrappedContexts[name];
wrapped = thunk.namespace.bind(wrapped, thunk.context);
});
return wrapped;
}
wrapEmitter(emitter, attach, bind);
};
/**
* If an error comes out of a namespace, it will have a context attached to it.
* This function knows how to find it.
*
* @param {Error} exception Possibly annotated error.
*/
Namespace.prototype.fromException = function fromException(exception) {
return exception[ERROR_SYMBOL];
};
function getNamespace(name) {
return process.namespaces[name];
}
function createNamespace(name) {
assert.ok(name, 'namespace must be given a name.');
if (DEBUG_CLS_HOOKED) {
debug2('CREATING NAMESPACE ' + name);
}
let namespace = new Namespace(name);
namespace.id = currentUid;
asyncHook.addHooks({
init(uid, handle, provider, parentUid, parentHandle) {
//parentUid = parentUid || currentUid; // Suggested usage but appears to work better for tracing modules.
currentUid = uid;
//CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec
if (parentUid) {
namespace._contexts.set(uid, namespace._contexts.get(parentUid));
if (DEBUG_CLS_HOOKED) {
debug2('PARENTID: ' + name + ' uid:' + uid + ' parent:' + parentUid + ' provider:' + provider);
}
} else {
namespace._contexts.set(currentUid, namespace.active);
}
if (DEBUG_CLS_HOOKED) {
debug2('INIT ' + name + ' uid:' + uid + ' parent:' + parentUid + ' provider:' + invertedProviders[provider]
+ ' active:' + util.inspect(namespace.active, true));
}
},
pre(uid, handle) {
currentUid = uid;
let context = namespace._contexts.get(uid);
if (context) {
if (DEBUG_CLS_HOOKED) {
debug2(' PRE ' + name + ' uid:' + uid + ' handle:' + getFunctionName(handle) + ' context:' +
util.inspect(context));
}
namespace.enter(context);
} else {
if (DEBUG_CLS_HOOKED) {
debug2(' PRE MISSING CONTEXT ' + name + ' uid:' + uid + ' handle:' + getFunctionName(handle));
}
}
},
post(uid, handle) {
currentUid = uid;
let context = namespace._contexts.get(uid);
if (context) {
if (DEBUG_CLS_HOOKED) {
debug2(' POST ' + name + ' uid:' + uid + ' handle:' + getFunctionName(handle) + ' context:' +
util.inspect(context));
}
namespace.exit(context);
} else {
if (DEBUG_CLS_HOOKED) {
debug2(' POST MISSING CONTEXT ' + name + ' uid:' + uid + ' handle:' + getFunctionName(handle));
}
}
},
destroy(uid) {
currentUid = uid;
if (DEBUG_CLS_HOOKED) {
debug2('DESTROY ' + name + ' uid:' + uid + ' context:' + util.inspect(namespace._contexts.get(currentUid))
+ ' active:' + util.inspect(namespace.active, true));
}
namespace._contexts.delete(uid);
}
});
process.namespaces[name] = namespace;
return namespace;
}
function destroyNamespace(name) {
let namespace = getNamespace(name);
assert.ok(namespace, 'can\'t delete nonexistent namespace! "' + name + '"');
assert.ok(namespace.id, 'don\'t assign to process.namespaces directly! ' + util.inspect(namespace));
process.namespaces[name] = null;
}
function reset() {
// must unregister async listeners
if (process.namespaces) {
Object.keys(process.namespaces).forEach(function (name) {
destroyNamespace(name);
});
}
process.namespaces = Object.create(null);
}
process.namespaces = {};
if (asyncHook._state && !asyncHook._state.enabled) {
asyncHook.enable();
}
function debug2(msg) {
if (process.env.DEBUG) {
process._rawDebug(msg);
}
}
/*function debug(from, ns) {
process._rawDebug('DEBUG: ' + util.inspect({
from: from,
currentUid: currentUid,
context: ns ? ns._contexts.get(currentUid) : 'no ns'
}, true, 2, true));
}*/
function getFunctionName(fn) {
if (!fn) {
return fn;
}
if (typeof fn === 'function') {
if (fn.name) {
return fn.name;
}
return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
} else if (fn.constructor && fn.constructor.name) {
return fn.constructor.name;
}
}
// Add back to callstack
if (DEBUG_CLS_HOOKED) {
var stackChain = require('stack-chain');
for (var modifier in stackChain.filter._modifiers) {
stackChain.filter.deattach(modifier);
}
}
+477
View File
@@ -0,0 +1,477 @@
/* eslint-disable max-len */
'use strict';
const util = require('util');
const assert = require('assert');
const wrapEmitter = require('emitter-listener');
const async_hooks = require('async_hooks');
const CONTEXTS_SYMBOL = 'cls@contexts';
const ERROR_SYMBOL = 'error@context';
const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED;
let currentUid = -1;
module.exports = {
getNamespace: getNamespace,
createNamespace: createNamespace,
destroyNamespace: destroyNamespace,
reset: reset,
ERROR_SYMBOL: ERROR_SYMBOL
};
function Namespace(name) {
this.name = name;
// changed in 2.7: no default context
this.active = null;
this._set = [];
this.id = null;
this._contexts = new Map();
this._indent = 0;
}
Namespace.prototype.set = function set(key, value) {
if (!this.active) {
throw new Error('No context available. ns.run() or ns.bind() must be called first.');
}
this.active[key] = value;
if (DEBUG_CLS_HOOKED) {
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(indentStr + 'CONTEXT-SET KEY:' + key + '=' + value + ' in ns:' + this.name + ' currentUid:' + currentUid + ' active:' + util.inspect(this.active, {showHidden:true, depth:2, colors:true}));
}
return value;
};
Namespace.prototype.get = function get(key) {
if (!this.active) {
if (DEBUG_CLS_HOOKED) {
const asyncHooksCurrentId = async_hooks.currentId();
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
//debug2(indentStr + 'CONTEXT-GETTING KEY NO ACTIVE NS:' + key + '=undefined' + ' (' + this.name + ') currentUid:' + currentUid + ' active:' + util.inspect(this.active, {showHidden:true, depth:2, colors:true}));
debug2(`${indentStr}CONTEXT-GETTING KEY NO ACTIVE NS: (${this.name}) ${key}=undefined currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length}`);
}
return undefined;
}
if (DEBUG_CLS_HOOKED) {
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(indentStr + 'CONTEXT-GETTING KEY:' + key + '=' + this.active[key] + ' (' + this.name + ') currentUid:' + currentUid + ' active:' + util.inspect(this.active, {showHidden:true, depth:2, colors:true}));
debug2(`${indentStr}CONTEXT-GETTING KEY: (${this.name}) ${key}=${this.active[key]} currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length} active:${util.inspect(this.active)}`);
}
return this.active[key];
};
Namespace.prototype.createContext = function createContext() {
// Prototype inherit existing context if created a new child context within existing context.
let context = Object.create(this.active ? this.active : Object.prototype);
context._ns_name = this.name;
context.id = currentUid;
if (DEBUG_CLS_HOOKED) {
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(`${indentStr}CONTEXT-CREATED Context: (${this.name}) currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length} context:${util.inspect(context, {showHidden:true, depth:2, colors:true})}`);
}
return context;
};
Namespace.prototype.run = function run(fn) {
let context = this.createContext();
this.enter(context);
try {
if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(`${indentStr}CONTEXT-RUN BEGIN: (${this.name}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${this._set.length} context:${util.inspect(context)}`);
}
fn(context);
return context;
} catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;
}
throw exception;
} finally {
if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(`${indentStr}CONTEXT-RUN END: (${this.name}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${this._set.length} ${util.inspect(context)}`);
}
this.exit(context);
}
};
Namespace.prototype.runAndReturn = function runAndReturn(fn) {
let value;
this.run(function (context) {
value = fn(context);
});
return value;
};
/**
* Uses global Promise and assumes Promise is cls friendly or wrapped already.
* @param {function} fn
* @returns {*}
*/
Namespace.prototype.runPromise = function runPromise(fn) {
let context = this.createContext();
this.enter(context);
let promise = fn(context);
if (!promise || !promise.then || !promise.catch) {
throw new Error('fn must return a promise.');
}
if (DEBUG_CLS_HOOKED) {
debug2('CONTEXT-runPromise BEFORE: (' + this.name + ') currentUid:' + currentUid + ' len:' + this._set.length + ' ' + util.inspect(context));
}
return promise
.then(result => {
if (DEBUG_CLS_HOOKED) {
debug2('CONTEXT-runPromise AFTER then: (' + this.name + ') currentUid:' + currentUid + ' len:' + this._set.length + ' ' + util.inspect(context));
}
this.exit(context);
return result;
})
.catch(err => {
err[ERROR_SYMBOL] = context;
if (DEBUG_CLS_HOOKED) {
debug2('CONTEXT-runPromise AFTER catch: (' + this.name + ') currentUid:' + currentUid + ' len:' + this._set.length + ' ' + util.inspect(context));
}
this.exit(context);
throw err;
});
};
Namespace.prototype.bind = function bindFactory(fn, context) {
if (!context) {
if (!this.active) {
context = this.createContext();
} else {
context = this.active;
}
}
let self = this;
return function clsBind() {
self.enter(context);
try {
return fn.apply(this, arguments);
} catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;
}
throw exception;
} finally {
self.exit(context);
}
};
};
Namespace.prototype.enter = function enter(context) {
assert.ok(context, 'context must be provided for entering');
if (DEBUG_CLS_HOOKED) {
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(`${indentStr}CONTEXT-ENTER: (${this.name}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${this._set.length} ${util.inspect(context)}`);
}
this._set.push(this.active);
this.active = context;
};
Namespace.prototype.exit = function exit(context) {
assert.ok(context, 'context must be provided for exiting');
if (DEBUG_CLS_HOOKED) {
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(`${indentStr}CONTEXT-EXIT: (${this.name}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${this._set.length} ${util.inspect(context)}`);
}
// Fast path for most exits that are at the top of the stack
if (this.active === context) {
assert.ok(this._set.length, 'can\'t remove top context');
this.active = this._set.pop();
return;
}
// Fast search in the stack using lastIndexOf
let index = this._set.lastIndexOf(context);
if (index < 0) {
if (DEBUG_CLS_HOOKED) {
debug2('??ERROR?? context exiting but not entered - ignoring: ' + util.inspect(context));
}
assert.ok(index >= 0, 'context not currently entered; can\'t exit. \n' + util.inspect(this) + '\n' + util.inspect(context));
} else {
assert.ok(index, 'can\'t remove top context');
this._set.splice(index, 1);
}
};
Namespace.prototype.bindEmitter = function bindEmitter(emitter) {
assert.ok(emitter.on && emitter.addListener && emitter.emit, 'can only bind real EEs');
let namespace = this;
let thisSymbol = 'context@' + this.name;
// Capture the context active at the time the emitter is bound.
function attach(listener) {
if (!listener) {
return;
}
if (!listener[CONTEXTS_SYMBOL]) {
listener[CONTEXTS_SYMBOL] = Object.create(null);
}
listener[CONTEXTS_SYMBOL][thisSymbol] = {
namespace: namespace,
context: namespace.active
};
}
// At emit time, bind the listener within the correct context.
function bind(unwrapped) {
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) {
return unwrapped;
}
let wrapped = unwrapped;
let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL];
Object.keys(unwrappedContexts).forEach(function (name) {
let thunk = unwrappedContexts[name];
wrapped = thunk.namespace.bind(wrapped, thunk.context);
});
return wrapped;
}
wrapEmitter(emitter, attach, bind);
};
/**
* If an error comes out of a namespace, it will have a context attached to it.
* This function knows how to find it.
*
* @param {Error} exception Possibly annotated error.
*/
Namespace.prototype.fromException = function fromException(exception) {
return exception[ERROR_SYMBOL];
};
function getNamespace(name) {
return process.namespaces[name];
}
function createNamespace(name) {
assert.ok(name, 'namespace must be given a name.');
if (DEBUG_CLS_HOOKED) {
debug2(`NS-CREATING NAMESPACE (${name})`);
}
let namespace = new Namespace(name);
namespace.id = currentUid;
const hook = async_hooks.createHook({
init(asyncId, type, triggerId, resource) {
currentUid = async_hooks.executionAsyncId();
//CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec
// let initContext = namespace.active;
// if(!initContext && triggerId) {
// let parentContext = namespace._contexts.get(triggerId);
// if (parentContext) {
// namespace.active = parentContext;
// namespace._contexts.set(currentUid, parentContext);
// if (DEBUG_CLS_HOOKED) {
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
// debug2(`${indentStr}INIT [${type}] (${name}) WITH PARENT CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
// }
// } else if (DEBUG_CLS_HOOKED) {
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
// debug2(`${indentStr}INIT [${type}] (${name}) MISSING CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
// }
// }else {
// namespace._contexts.set(currentUid, namespace.active);
// if (DEBUG_CLS_HOOKED) {
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
// debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
// }
// }
if(namespace.active) {
namespace._contexts.set(asyncId, namespace.active);
if (DEBUG_CLS_HOOKED) {
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} resource:${resource}`);
}
}else if(currentUid === 0){
// CurrentId will be 0 when triggered from C++. Promise events
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
const triggerId = async_hooks.triggerAsyncId();
const triggerIdContext = namespace._contexts.get(triggerId);
if (triggerIdContext) {
namespace._contexts.set(asyncId, triggerIdContext);
if (DEBUG_CLS_HOOKED) {
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}INIT USING CONTEXT FROM TRIGGERID [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, { showHidden: true, depth: 2, colors: true })} resource:${resource}`);
}
} else if (DEBUG_CLS_HOOKED) {
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}INIT MISSING CONTEXT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, { showHidden: true, depth: 2, colors: true })} resource:${resource}`);
}
}
if(DEBUG_CLS_HOOKED && type === 'PROMISE'){
debug2(util.inspect(resource, {showHidden: true}));
const parentId = resource.parentId;
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}INIT RESOURCE-PROMISE [${type}] (${name}) parentId:${parentId} asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} resource:${resource}`);
}
},
before(asyncId) {
currentUid = async_hooks.executionAsyncId();
let context;
/*
if(currentUid === 0){
// CurrentId will be 0 when triggered from C++. Promise events
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
//const triggerId = async_hooks.triggerAsyncId();
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
}else{
context = namespace._contexts.get(currentUid);
}
*/
//HACK to work with promises until they are fixed in node > 8.1.1
context = namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid);
if (context) {
if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}BEFORE (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} context:${util.inspect(context)}`);
namespace._indent += 2;
}
namespace.enter(context);
} else if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}BEFORE MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} namespace._contexts:${util.inspect(namespace._contexts, {showHidden:true, depth:2, colors:true})}`);
namespace._indent += 2;
}
},
after(asyncId) {
currentUid = async_hooks.executionAsyncId();
let context; // = namespace._contexts.get(currentUid);
/*
if(currentUid === 0){
// CurrentId will be 0 when triggered from C++. Promise events
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
//const triggerId = async_hooks.triggerAsyncId();
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
}else{
context = namespace._contexts.get(currentUid);
}
*/
//HACK to work with promises until they are fixed in node > 8.1.1
context = namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid);
if (context) {
if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
namespace._indent -= 2;
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}AFTER (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} context:${util.inspect(context)}`);
}
namespace.exit(context);
} else if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
namespace._indent -= 2;
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}AFTER MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} context:${util.inspect(context)}`);
}
},
destroy(asyncId) {
currentUid = async_hooks.executionAsyncId();
if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
debug2(`${indentStr}DESTROY (${name}) currentUid:${currentUid} asyncId:${asyncId} triggerId:${triggerId} active:${util.inspect(namespace.active, {showHidden:true, depth:2, colors:true})} context:${util.inspect(namespace._contexts.get(currentUid))}`);
}
namespace._contexts.delete(asyncId);
}
});
hook.enable();
process.namespaces[name] = namespace;
return namespace;
}
function destroyNamespace(name) {
let namespace = getNamespace(name);
assert.ok(namespace, 'can\'t delete nonexistent namespace! "' + name + '"');
assert.ok(namespace.id, 'don\'t assign to process.namespaces directly! ' + util.inspect(namespace));
process.namespaces[name] = null;
}
function reset() {
// must unregister async listeners
if (process.namespaces) {
Object.keys(process.namespaces).forEach(function (name) {
destroyNamespace(name);
});
}
process.namespaces = Object.create(null);
}
process.namespaces = {};
//const fs = require('fs');
function debug2(...args) {
if (DEBUG_CLS_HOOKED) {
//fs.writeSync(1, `${util.format(...args)}\n`);
process._rawDebug(`${util.format(...args)}`);
}
}
/*function getFunctionName(fn) {
if (!fn) {
return fn;
}
if (typeof fn === 'function') {
if (fn.name) {
return fn.name;
}
return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
} else if (fn.constructor && fn.constructor.name) {
return fn.constructor.name;
}
}*/
+15
View File
@@ -0,0 +1,15 @@
'use strict';
const semver = require('semver');
/**
* In order to increase node version support, this loads the version of context
* that is appropriate for the version of on nodejs that is running.
* Node < v8 - uses AsyncWrap and async-hooks-jl
* Node >= v8 - uses native async-hooks
*/
if(process && semver.gte(process.versions.node, '8.0.0')){
module.exports = require('./context');
}else{
module.exports = require('./context-legacy');
}
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver
+15
View File
@@ -0,0 +1,15 @@
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+412
View File
@@ -0,0 +1,412 @@
semver(1) -- The semantic versioner for npm
===========================================
## Install
```bash
npm install --save semver
````
## Usage
As a node module:
```js
const semver = require('semver')
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
semver.minVersion('>=1.0.0') // '1.0.0'
semver.valid(semver.coerce('v2')) // '2.0.0'
semver.valid(semver.coerce('42.6.7.9.3-alpha')) // '42.6.7'
```
As a command-line utility:
```
$ semver -h
A JavaScript implementation of the https://semver.org/ specification
Copyright Isaac Z. Schlueter
Usage: semver [options] <version> [<version> [...]]
Prints valid versions sorted by SemVer precedence
Options:
-r --range <range>
Print versions that match the specified range.
-i --increment [<level>]
Increment a version by the specified level. Level can
be one of: major, minor, patch, premajor, preminor,
prepatch, or prerelease. Default level is 'patch'.
Only one version may be specified.
--preid <identifier>
Identifier to be used to prefix premajor, preminor,
prepatch or prerelease version increments.
-l --loose
Interpret versions and ranges loosely
-p --include-prerelease
Always include prerelease versions in range matching
-c --coerce
Coerce a string into SemVer if possible
(does not imply --loose)
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
If no satisfying versions are found, then exits failure.
Versions are printed in ascending order, so supplying
multiple versions to the utility will just sort them.
```
## Versions
A "version" is described by the `v2.0.0` specification found at
<https://semver.org/>.
A leading `"="` or `"v"` character is stripped off and ignored.
## Ranges
A `version range` is a set of `comparators` which specify versions
that satisfy the range.
A `comparator` is composed of an `operator` and a `version`. The set
of primitive `operators` is:
* `<` Less than
* `<=` Less than or equal to
* `>` Greater than
* `>=` Greater than or equal to
* `=` Equal. If no operator is specified, then equality is assumed,
so this operator is optional, but MAY be included.
For example, the comparator `>=1.2.7` would match the versions
`1.2.7`, `1.2.8`, `2.5.3`, and `1.3.9`, but not the versions `1.2.6`
or `1.1.0`.
Comparators can be joined by whitespace to form a `comparator set`,
which is satisfied by the **intersection** of all of the comparators
it includes.
A range is composed of one or more comparator sets, joined by `||`. A
version matches a range if and only if every comparator in at least
one of the `||`-separated comparator sets is satisfied by the version.
For example, the range `>=1.2.7 <1.3.0` would match the versions
`1.2.7`, `1.2.8`, and `1.2.99`, but not the versions `1.2.6`, `1.3.0`,
or `1.1.0`.
The range `1.2.7 || >=1.2.9 <2.0.0` would match the versions `1.2.7`,
`1.2.9`, and `1.4.6`, but not the versions `1.2.8` or `2.0.0`.
### Prerelease Tags
If a version has a prerelease tag (for example, `1.2.3-alpha.3`) then
it will only be allowed to satisfy comparator sets if at least one
comparator with the same `[major, minor, patch]` tuple also has a
prerelease tag.
For example, the range `>1.2.3-alpha.3` would be allowed to match the
version `1.2.3-alpha.7`, but it would *not* be satisfied by
`3.4.5-alpha.9`, even though `3.4.5-alpha.9` is technically "greater
than" `1.2.3-alpha.3` according to the SemVer sort rules. The version
range only accepts prerelease tags on the `1.2.3` version. The
version `3.4.5` *would* satisfy the range, because it does not have a
prerelease flag, and `3.4.5` is greater than `1.2.3-alpha.7`.
The purpose for this behavior is twofold. First, prerelease versions
frequently are updated very quickly, and contain many breaking changes
that are (by the author's design) not yet fit for public consumption.
Therefore, by default, they are excluded from range matching
semantics.
Second, a user who has opted into using a prerelease version has
clearly indicated the intent to use *that specific* set of
alpha/beta/rc versions. By including a prerelease tag in the range,
the user is indicating that they are aware of the risk. However, it
is still not appropriate to assume that they have opted into taking a
similar risk on the *next* set of prerelease versions.
Note that this behavior can be suppressed (treating all prerelease
versions as if they were normal versions, for the purpose of range
matching) by setting the `includePrerelease` flag on the options
object to any
[functions](https://github.com/npm/node-semver#functions) that do
range matching.
#### Prerelease Identifiers
The method `.inc` takes an additional `identifier` string argument that
will append the value of the string as a prerelease identifier:
```javascript
semver.inc('1.2.3', 'prerelease', 'beta')
// '1.2.4-beta.0'
```
command-line example:
```bash
$ semver 1.2.3 -i prerelease --preid beta
1.2.4-beta.0
```
Which then can be used to increment further:
```bash
$ semver 1.2.4-beta.0 -i prerelease
1.2.4-beta.1
```
### Advanced Range Syntax
Advanced range syntax desugars to primitive comparators in
deterministic ways.
Advanced ranges may be combined in the same way as primitive
comparators using white space or `||`.
#### Hyphen Ranges `X.Y.Z - A.B.C`
Specifies an inclusive set.
* `1.2.3 - 2.3.4` := `>=1.2.3 <=2.3.4`
If a partial version is provided as the first version in the inclusive
range, then the missing pieces are replaced with zeroes.
* `1.2 - 2.3.4` := `>=1.2.0 <=2.3.4`
If a partial version is provided as the second version in the
inclusive range, then all versions that start with the supplied parts
of the tuple are accepted, but nothing that would be greater than the
provided tuple parts.
* `1.2.3 - 2.3` := `>=1.2.3 <2.4.0`
* `1.2.3 - 2` := `>=1.2.3 <3.0.0`
#### X-Ranges `1.2.x` `1.X` `1.2.*` `*`
Any of `X`, `x`, or `*` may be used to "stand in" for one of the
numeric values in the `[major, minor, patch]` tuple.
* `*` := `>=0.0.0` (Any version satisfies)
* `1.x` := `>=1.0.0 <2.0.0` (Matching major version)
* `1.2.x` := `>=1.2.0 <1.3.0` (Matching major and minor versions)
A partial version range is treated as an X-Range, so the special
character is in fact optional.
* `""` (empty string) := `*` := `>=0.0.0`
* `1` := `1.x.x` := `>=1.0.0 <2.0.0`
* `1.2` := `1.2.x` := `>=1.2.0 <1.3.0`
#### Tilde Ranges `~1.2.3` `~1.2` `~1`
Allows patch-level changes if a minor version is specified on the
comparator. Allows minor-level changes if not.
* `~1.2.3` := `>=1.2.3 <1.(2+1).0` := `>=1.2.3 <1.3.0`
* `~1.2` := `>=1.2.0 <1.(2+1).0` := `>=1.2.0 <1.3.0` (Same as `1.2.x`)
* `~1` := `>=1.0.0 <(1+1).0.0` := `>=1.0.0 <2.0.0` (Same as `1.x`)
* `~0.2.3` := `>=0.2.3 <0.(2+1).0` := `>=0.2.3 <0.3.0`
* `~0.2` := `>=0.2.0 <0.(2+1).0` := `>=0.2.0 <0.3.0` (Same as `0.2.x`)
* `~0` := `>=0.0.0 <(0+1).0.0` := `>=0.0.0 <1.0.0` (Same as `0.x`)
* `~1.2.3-beta.2` := `>=1.2.3-beta.2 <1.3.0` Note that prereleases in
the `1.2.3` version will be allowed, if they are greater than or
equal to `beta.2`. So, `1.2.3-beta.4` would be allowed, but
`1.2.4-beta.2` would not, because it is a prerelease of a
different `[major, minor, patch]` tuple.
#### Caret Ranges `^1.2.3` `^0.2.5` `^0.0.4`
Allows changes that do not modify the left-most non-zero digit in the
`[major, minor, patch]` tuple. In other words, this allows patch and
minor updates for versions `1.0.0` and above, patch updates for
versions `0.X >=0.1.0`, and *no* updates for versions `0.0.X`.
Many authors treat a `0.x` version as if the `x` were the major
"breaking-change" indicator.
Caret ranges are ideal when an author may make breaking changes
between `0.2.4` and `0.3.0` releases, which is a common practice.
However, it presumes that there will *not* be breaking changes between
`0.2.4` and `0.2.5`. It allows for changes that are presumed to be
additive (but non-breaking), according to commonly observed practices.
* `^1.2.3` := `>=1.2.3 <2.0.0`
* `^0.2.3` := `>=0.2.3 <0.3.0`
* `^0.0.3` := `>=0.0.3 <0.0.4`
* `^1.2.3-beta.2` := `>=1.2.3-beta.2 <2.0.0` Note that prereleases in
the `1.2.3` version will be allowed, if they are greater than or
equal to `beta.2`. So, `1.2.3-beta.4` would be allowed, but
`1.2.4-beta.2` would not, because it is a prerelease of a
different `[major, minor, patch]` tuple.
* `^0.0.3-beta` := `>=0.0.3-beta <0.0.4` Note that prereleases in the
`0.0.3` version *only* will be allowed, if they are greater than or
equal to `beta`. So, `0.0.3-pr.2` would be allowed.
When parsing caret ranges, a missing `patch` value desugars to the
number `0`, but will allow flexibility within that value, even if the
major and minor versions are both `0`.
* `^1.2.x` := `>=1.2.0 <2.0.0`
* `^0.0.x` := `>=0.0.0 <0.1.0`
* `^0.0` := `>=0.0.0 <0.1.0`
A missing `minor` and `patch` values will desugar to zero, but also
allow flexibility within those values, even if the major version is
zero.
* `^1.x` := `>=1.0.0 <2.0.0`
* `^0.x` := `>=0.0.0 <1.0.0`
### Range Grammar
Putting all this together, here is a Backus-Naur grammar for ranges,
for the benefit of parser authors:
```bnf
range-set ::= range ( logical-or range ) *
logical-or ::= ( ' ' ) * '||' ( ' ' ) *
range ::= hyphen | simple ( ' ' simple ) * | ''
hyphen ::= partial ' - ' partial
simple ::= primitive | partial | tilde | caret
primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
xr ::= 'x' | 'X' | '*' | nr
nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
tilde ::= '~' partial
caret ::= '^' partial
qualifier ::= ( '-' pre )? ( '+' build )?
pre ::= parts
build ::= parts
parts ::= part ( '.' part ) *
part ::= nr | [-0-9A-Za-z]+
```
## Functions
All methods and classes take a final `options` object argument. All
options in this object are `false` by default. The options supported
are:
- `loose` Be more forgiving about not-quite-valid semver strings.
(Any resulting output will always be 100% strict compliant, of
course.) For backwards compatibility reasons, if the `options`
argument is a boolean value instead of an object, it is interpreted
to be the `loose` param.
- `includePrerelease` Set to suppress the [default
behavior](https://github.com/npm/node-semver#prerelease-tags) of
excluding prerelease tagged versions from ranges unless they are
explicitly opted into.
Strict-mode Comparators and Ranges will be strict about the SemVer
strings that they parse.
* `valid(v)`: Return the parsed version, or null if it's not valid.
* `inc(v, release)`: Return the version incremented by the release
type (`major`, `premajor`, `minor`, `preminor`, `patch`,
`prepatch`, or `prerelease`), or null if it's not valid
* `premajor` in one call will bump the version up to the next major
version and down to a prerelease of that major version.
`preminor`, and `prepatch` work the same way.
* If called from a non-prerelease version, the `prerelease` will work the
same as `prepatch`. It increments the patch version, then makes a
prerelease. If the input version is already a prerelease it simply
increments it.
* `prerelease(v)`: Returns an array of prerelease components, or null
if none exist. Example: `prerelease('1.2.3-alpha.1') -> ['alpha', 1]`
* `major(v)`: Return the major version number.
* `minor(v)`: Return the minor version number.
* `patch(v)`: Return the patch version number.
* `intersects(r1, r2, loose)`: Return true if the two supplied ranges
or comparators intersect.
* `parse(v)`: Attempt to parse a string as a semantic version, returning either
a `SemVer` object or `null`.
### Comparison
* `gt(v1, v2)`: `v1 > v2`
* `gte(v1, v2)`: `v1 >= v2`
* `lt(v1, v2)`: `v1 < v2`
* `lte(v1, v2)`: `v1 <= v2`
* `eq(v1, v2)`: `v1 == v2` This is true if they're logically equivalent,
even if they're not the exact same string. You already know how to
compare strings.
* `neq(v1, v2)`: `v1 != v2` The opposite of `eq`.
* `cmp(v1, comparator, v2)`: Pass in a comparison string, and it'll call
the corresponding function above. `"==="` and `"!=="` do simple
string comparison, but are included for completeness. Throws if an
invalid comparison string is provided.
* `compare(v1, v2)`: Return `0` if `v1 == v2`, or `1` if `v1` is greater, or `-1` if
`v2` is greater. Sorts in ascending order if passed to `Array.sort()`.
* `rcompare(v1, v2)`: The reverse of compare. Sorts an array of versions
in descending order when passed to `Array.sort()`.
* `diff(v1, v2)`: Returns difference between two versions by the release type
(`major`, `premajor`, `minor`, `preminor`, `patch`, `prepatch`, or `prerelease`),
or null if the versions are the same.
### Comparators
* `intersects(comparator)`: Return true if the comparators intersect
### Ranges
* `validRange(range)`: Return the valid range or null if it's not valid
* `satisfies(version, range)`: Return true if the version satisfies the
range.
* `maxSatisfying(versions, range)`: Return the highest version in the list
that satisfies the range, or `null` if none of them do.
* `minSatisfying(versions, range)`: Return the lowest version in the list
that satisfies the range, or `null` if none of them do.
* `minVersion(range)`: Return the lowest version that can possibly match
the given range.
* `gtr(version, range)`: Return `true` if version is greater than all the
versions possible in the range.
* `ltr(version, range)`: Return `true` if version is less than all the
versions possible in the range.
* `outside(version, range, hilo)`: Return true if the version is outside
the bounds of the range in either the high or low direction. The
`hilo` argument must be either the string `'>'` or `'<'`. (This is
the function called by `gtr` and `ltr`.)
* `intersects(range)`: Return true if any of the ranges comparators intersect
Note that, since ranges may be non-contiguous, a version might not be
greater than a range, less than a range, *or* satisfy a range! For
example, the range `1.2 <1.2.9 || >2.0.0` would have a hole from `1.2.9`
until `2.0.0`, so the version `1.2.10` would not be greater than the
range (because `2.0.1` satisfies, which is higher), nor less than the
range (since `1.2.8` satisfies, which is lower), and it also does not
satisfy the range.
If you want to know if a version satisfies or does not satisfy a
range, use the `satisfies(version, range)` function.
### Coercion
* `coerce(version)`: Coerces a string to semver if possible
This aims to provide a very forgiving translation of a non-semver string to
semver. It looks for the first digit in a string, and consumes all
remaining characters which satisfy at least a partial semver (e.g., `1`,
`1.2`, `1.2.3`) up to the max permitted length (256 characters). Longer
versions are simply truncated (`4.6.3.9.2-alpha2` becomes `4.6.3`). All
surrounding text is simply ignored (`v3.4 replaces v3.3.1` becomes
`3.4.0`). Only text which lacks digits will fail coercion (`version one`
is not valid). The maximum length for any semver component considered for
coercion is 16 characters; longer components will be ignored
(`10000000000000000.4.7.4` becomes `4.7.4`). The maximum value for any
semver component is `Number.MAX_SAFE_INTEGER || (2**53 - 1)`; higher value
components are invalid (`9999999999999999.4.7.4` is likely invalid).
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env node
// Standalone semver comparison program.
// Exits successfully and prints matching version(s) if
// any supplied version is valid and passes all tests.
var argv = process.argv.slice(2)
var versions = []
var range = []
var inc = null
var version = require('../package.json').version
var loose = false
var includePrerelease = false
var coerce = false
var identifier
var semver = require('../semver')
var reverse = false
var options = {}
main()
function main () {
if (!argv.length) return help()
while (argv.length) {
var a = argv.shift()
var indexOfEqualSign = a.indexOf('=')
if (indexOfEqualSign !== -1) {
a = a.slice(0, indexOfEqualSign)
argv.unshift(a.slice(indexOfEqualSign + 1))
}
switch (a) {
case '-rv': case '-rev': case '--rev': case '--reverse':
reverse = true
break
case '-l': case '--loose':
loose = true
break
case '-p': case '--include-prerelease':
includePrerelease = true
break
case '-v': case '--version':
versions.push(argv.shift())
break
case '-i': case '--inc': case '--increment':
switch (argv[0]) {
case 'major': case 'minor': case 'patch': case 'prerelease':
case 'premajor': case 'preminor': case 'prepatch':
inc = argv.shift()
break
default:
inc = 'patch'
break
}
break
case '--preid':
identifier = argv.shift()
break
case '-r': case '--range':
range.push(argv.shift())
break
case '-c': case '--coerce':
coerce = true
break
case '-h': case '--help': case '-?':
return help()
default:
versions.push(a)
break
}
}
var options = { loose: loose, includePrerelease: includePrerelease }
versions = versions.map(function (v) {
return coerce ? (semver.coerce(v) || { version: v }).version : v
}).filter(function (v) {
return semver.valid(v)
})
if (!versions.length) return fail()
if (inc && (versions.length !== 1 || range.length)) { return failInc() }
for (var i = 0, l = range.length; i < l; i++) {
versions = versions.filter(function (v) {
return semver.satisfies(v, range[i], options)
})
if (!versions.length) return fail()
}
return success(versions)
}
function failInc () {
console.error('--inc can only be used on a single version with no range')
fail()
}
function fail () { process.exit(1) }
function success () {
var compare = reverse ? 'rcompare' : 'compare'
versions.sort(function (a, b) {
return semver[compare](a, b, options)
}).map(function (v) {
return semver.clean(v, options)
}).map(function (v) {
return inc ? semver.inc(v, inc, options, identifier) : v
}).forEach(function (v, i, _) { console.log(v) })
}
function help () {
console.log(['SemVer ' + version,
'',
'A JavaScript implementation of the https://semver.org/ specification',
'Copyright Isaac Z. Schlueter',
'',
'Usage: semver [options] <version> [<version> [...]]',
'Prints valid versions sorted by SemVer precedence',
'',
'Options:',
'-r --range <range>',
' Print versions that match the specified range.',
'',
'-i --increment [<level>]',
' Increment a version by the specified level. Level can',
' be one of: major, minor, patch, premajor, preminor,',
" prepatch, or prerelease. Default level is 'patch'.",
' Only one version may be specified.',
'',
'--preid <identifier>',
' Identifier to be used to prefix premajor, preminor,',
' prepatch or prerelease version increments.',
'',
'-l --loose',
' Interpret versions and ranges loosely',
'',
'-p --include-prerelease',
' Always include prerelease versions in range matching',
'',
'-c --coerce',
' Coerce a string into SemVer if possible',
' (does not imply --loose)',
'',
'Program exits successfully if any valid version satisfies',
'all supplied ranges, and prints all satisfying versions.',
'',
'If no satisfying versions are found, then exits failure.',
'',
'Versions are printed in ascending order, so supplying',
'multiple versions to the utility will just sort them.'
].join('\n'))
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "semver",
"version": "5.7.2",
"description": "The semantic version parser used by npm.",
"main": "semver.js",
"scripts": {
"test": "tap test/ --100 --timeout=30",
"lint": "echo linting disabled",
"postlint": "template-oss-check",
"template-oss-apply": "template-oss-apply --force",
"lintfix": "npm run lint -- --fix",
"snap": "tap test/ --100 --timeout=30",
"posttest": "npm run lint"
},
"devDependencies": {
"@npmcli/template-oss": "4.17.0",
"tap": "^12.7.0"
},
"license": "ISC",
"repository": {
"type": "git",
"url": "https://github.com/npm/node-semver.git"
},
"bin": {
"semver": "./bin/semver"
},
"files": [
"bin",
"range.bnf",
"semver.js"
],
"author": "GitHub Inc.",
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"content": "./scripts/template-oss",
"version": "4.17.0"
}
}
+16
View File
@@ -0,0 +1,16 @@
range-set ::= range ( logical-or range ) *
logical-or ::= ( ' ' ) * '||' ( ' ' ) *
range ::= hyphen | simple ( ' ' simple ) * | ''
hyphen ::= partial ' - ' partial
simple ::= primitive | partial | tilde | caret
primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
xr ::= 'x' | 'X' | '*' | nr
nr ::= '0' | [1-9] ( [0-9] ) *
tilde ::= '~' partial
caret ::= '^' partial
qualifier ::= ( '-' pre )? ( '+' build )?
pre ::= parts
build ::= parts
parts ::= part ( '.' part ) *
part ::= nr | [-0-9A-Za-z]+
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
{
"name": "cls-hooked",
"version": "4.2.2",
"description": "CLS using AsynWrap instead of async-listener - Node >= 4.7.0",
"main": "index.js",
"files": [
"index.js",
"context.js",
"context-legacy.js"
],
"scripts": {
"test": "mocha test/*.js & tap test/tap/*.tap.js",
"test-mocha": "mocha test/*.js",
"test-tap": "tap test/tap/*.tap.js",
"debug": "node --nolazy --debug-brk=5858 "
},
"repository": {
"type": "git",
"url": "https://github.com/jeff-lewis/cls-hooked.git"
},
"keywords": [
"threading",
"shared",
"context"
],
"author": "Forrest L Norvell <ogd@aoaioxxysz.net>",
"contributors": [
"Tim Caswell <tim@creationix.com>",
"Forrest L Norvell <ogd@aoaioxxysz.net>",
"Jeff Lewis <jlewis@streetconnect.com>"
],
"license": "BSD-2-Clause",
"engineStrict": false,
"engines": {
"node": "^4.7 || >=6.9 || >=7.3 || >=8.2.1"
},
"dependencies": {
"async-hook-jl": "^1.7.6",
"emitter-listener": "^1.0.1",
"semver": "^5.4.1"
},
"devDependencies": {
"chai": "^4.1.0",
"mocha": "^3.4.2",
"sinon": "^2.3.8",
"sinon-chai": "^2.12.0",
"superagent": "^3.5.2",
"tap": "^10.7.0",
"tap-mocha-reporter": "3.0.6"
},
"false": {}
}