disposable-cls
Version:
Provides disposable continuation local storage for Node.js.
217 lines (216 loc) • 8.49 kB
JavaScript
;
var shimmer_1 = require("shimmer");
var ContextStackItem_1 = require("./ContextStackItem");
/**
* Reacts to Node.js asynchronous scheduling events and manages a scope stack that will
* be preserved across those asynchronous invocations.
*/
var ContextStack = (function () {
/**
* Initializes a new ContextStack.
*/
function ContextStack() {
ContextStack.activeContext = null;
}
Object.defineProperty(ContextStack, "activeContext", {
/**
* Gets the current context stack item.
*
* @returns The currently active context stack item.
*/
get: function () {
return process[ContextStack.ACTIVECONTEXT_PROCESS_SLOTNAME];
},
/**
* Sets the current context stack item.
*
* @param value The context stack item to make current.
*/
set: function (value) {
process[ContextStack.ACTIVECONTEXT_PROCESS_SLOTNAME] = value;
},
enumerable: true,
configurable: true
});
/**
* Called by Node.js when an asynchronous function is being scheduled.
*
* The contextual items pushed onto the managed scope stack are captured and maintained
* throughout the lifetime of the asynchronous function being scheduled.
*/
ContextStack.prototype.create = function () {
return new ContextStackItem_1.ContextStackItem(ContextStack.scopeStack.pop(), ContextStack.captureStackItem(ContextStack.activeContext));
};
/**
* Called by Node.js before an asynchronous function is to be executed.
*
* @param context Represents the current 'this' object.
* @param contextStackItem The context stack item that was created for the asynchronous
* function.
*/
ContextStack.prototype.before = function (context, contextStackItem) {
ContextStack.activeContext = contextStackItem;
};
/**
* Called by Node.js after an asynchronous function has executed.
*
* @param context Represents the current 'this' object.
* @param contextStackItem The context stack item that was created for the asynchronous
* function.
*/
ContextStack.prototype.after = function (context, contextStackItem) {
ContextStack.activeContext = ContextStack.releaseStackItem(contextStackItem);
};
/**
* Called by Node.js if an asynchronous function has thrown an error.
*
* @param contextStackItem The context stack item that was created for the asynchronous
* function.
* @param error The error that was thrown.
*/
ContextStack.prototype.error = function (contextStackItem, error) {
ContextStack.activeContext = ContextStack.releaseStackItem(contextStackItem);
};
/**
* Pushes contextual items onto the managed scope stack so that they can be captured as part
* of a context stack item when the next asynchronous function is being scheduled.
*
* @param contextItems The array of objects to capture.
*/
ContextStack.prototype.pushScope = function (contextItems) {
var scopeStackItem = {};
contextItems.forEach(function (contextItem) {
scopeStackItem[contextItem.constructor.name] = contextItem;
});
ContextStack.scopeStack.push(scopeStackItem);
};
/**
* Searches the current context stack for a contextual item that was captured for the given
* type of object.
*
* @param objectType The constructor function of the object that is required.
* @returns The object that is currently in scope, or 'undefined' if none was found.
*/
ContextStack.prototype.findContextObjectFromScope = function (objectType) {
var objectTypeName = objectType.name;
var context = ContextStack.activeContext;
var object = undefined;
while (context) {
object = context.data ? context.data[objectTypeName] : undefined;
if (object) {
return object;
}
context = context.parent;
}
return undefined;
};
/**
* Bind an EventEmitter to the currently active context stack.
*
* @param emitter The EventEmitter to bind.
*/
ContextStack.prototype.bindEventEmitter = function (emitter) {
var ACTIVECONTEXT_LISTENER_SLOTNAME = "__cls_capturedcontext";
var onAddListener = function (originalAddListenerFunc) {
return function (event, listener) {
listener[ACTIVECONTEXT_LISTENER_SLOTNAME] = ContextStack.captureStackItem(ContextStack.activeContext);
return originalAddListenerFunc.apply(this, arguments);
};
};
var onEmit = function (originalEmitFunc) {
return function (event) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (!this._events || !this._events[event]) {
return originalEmitFunc.apply(this, arguments);
}
var listener = this._events[event];
var currentContext = ContextStack.activeContext;
try {
ContextStack.activeContext = listener[ACTIVECONTEXT_LISTENER_SLOTNAME];
return originalEmitFunc.apply(this, arguments);
}
finally {
ContextStack.activeContext = currentContext;
}
};
};
var onRemoveListener = function (originalRemoveListenerFunc) {
return function (event, listener) {
ContextStack.releaseStackItem(listener[ACTIVECONTEXT_LISTENER_SLOTNAME]);
return originalRemoveListenerFunc.apply(this, arguments);
};
};
shimmer_1.wrap(emitter, "addListener", onAddListener);
shimmer_1.wrap(emitter, "on", onAddListener);
shimmer_1.wrap(emitter, "emit", onEmit);
shimmer_1.wrap(emitter, "removeListener", onRemoveListener);
};
/**
* Resets the managed scope stack.
*
* @returns A boolean flag indicating if the ContextStack could be reset.
*/
ContextStack.tryReset = function () {
if (ContextStack.activeContext) {
return false;
}
ContextStack.activeContext = null;
ContextStack.scopeStack.splice(0, ContextStack.scopeStack.length);
return true;
};
/**
* Captures a given context stack item by incrementing the reference counts across
* the entire stack.
*
* @param contextStackItem The context stack item that is to be captured.
* @returns The original context stack item.
*/
ContextStack.captureStackItem = function (contextStackItem) {
var context = contextStackItem;
while (context) {
context.addRef();
context = context.parent;
}
return contextStackItem;
};
/**
* Releases a given context stack item.
*
* @param contextStackItem The context stack item that is to be released.
* @returns The parent context stack item.
*/
ContextStack.releaseStackItem = function (contextStackItem) {
if (!contextStackItem) {
return null; // nothing to release
}
var context = contextStackItem;
while (context) {
if (context.release() === 0) {
ContextStack.disposeStackItemObjects(context.data);
}
context = context.parent;
}
return contextStackItem.parent;
};
/**
* Disposes of objects in a given object by calling their 'dispose()' function if they
* have one.
*
* @param data An object that will be enumerated.
*/
ContextStack.disposeStackItemObjects = function (data) {
for (var key in data) {
var object = data[key];
if (object["dispose"]) {
object.dispose();
}
}
};
ContextStack.ACTIVECONTEXT_PROCESS_SLOTNAME = "__cls_activecontext";
ContextStack.scopeStack = [];
return ContextStack;
}());
exports.ContextStack = ContextStack;