node-execution-context
Advanced tools
Comparing version
@@ -14,3 +14,3 @@ module.exports = { | ||
rules: { | ||
'no-console': 0, | ||
'no-console': 2, | ||
'no-var': 2, | ||
@@ -17,0 +17,0 @@ 'prefer-const': 2, |
const Context = require('../src'); | ||
const delay = (callback) => setTimeout(() => { | ||
const delay = (callback, timeout = 1000) => setTimeout(() => { | ||
callback(); | ||
}, 2000); | ||
}, timeout); | ||
@@ -11,5 +11,17 @@ class UserController { | ||
delay(() => { | ||
console.log('Callback : ', Context.get()); | ||
console.log('Callback : ', Context.get()); // { val: true } | ||
}); | ||
// Creates a dedicate domain context ( exclude this following chain from root context ) | ||
// Updates mae from domain will not effect root context. | ||
delay(() => { | ||
Context.create({ specific: true }, 'custom-domain'); | ||
delay(() => { | ||
console.log('Domain callback ', Context.get()) // { val: true, specific: true } | ||
Context.update({ inner: true }); | ||
}, 400); | ||
}, 4000) | ||
res.send(Context.get()); | ||
@@ -16,0 +28,0 @@ } |
@@ -17,4 +17,4 @@ const express = require('express'); | ||
app.listen(port, function(){ | ||
app.listen(port, () => { | ||
console.log('Server is running'); | ||
}); |
{ | ||
"name": "node-execution-context", | ||
"version": "1.1.7", | ||
"version": "2.0.0-beta.0", | ||
"description": "Provides execution context wrapper for node JS, can be used to create execution wrapper for handling requests and more", | ||
@@ -5,0 +5,0 @@ "author": "Oded Goldglas <odedglas@gmail.com>", |
@@ -60,3 +60,3 @@ # node-execution-context | ||
### create(initialContext?: object) | ||
### create(initialContext?: object, domain? :string) | ||
@@ -66,2 +66,4 @@ Creates for the current async resource an execution context entry identified with his asyncId. | ||
> When passing custom domain to this method, the trigger point and all of it's sub processes will be exposed to a standalone context won't effect / be effected by root context. | ||
### update(update: object) | ||
@@ -79,2 +81,6 @@ | ||
### configure(config: ExecutionContextConfig) : void | ||
Configures execution context settings. | ||
### monitor(): ExecutionMapUsage | ||
@@ -84,5 +90,18 @@ | ||
> Before calling `monitor`, you should `configure` execution context to monitor it's nodes. by default they are kept as lean as possible. | ||
```js | ||
const Context = require('node-execution-context'); | ||
// Startup | ||
Context.configure({ monitor: true }); | ||
// Later on | ||
const usage = Context.monitor(); | ||
console.log(usage); // Prints execution context usage report. | ||
``` | ||
### API Usage | ||
```js | ||
@@ -118,3 +137,2 @@ const Context = require('node-execution-context'); | ||
console.log(Context.get()); // outputs: {"value": true} | ||
console.log(Context.monitor) // Will result with monitor result | ||
}); | ||
@@ -126,6 +144,7 @@ }); | ||
| Code | when | | ||
| Code | When | | ||
|-|- | ||
| CONTEXT_ALREADY_DECLARED | When trying to `create` execution context, but current async resource already exists. | ||
| CONTEXT_DOES_NOT_EXISTS | When try to `get` / `update` the context, but it yet been created. | ||
| MONITOR_MISS_CONFIGURATION | When try to `monitor` without calling `configure` with monitoring option. | ||
@@ -6,3 +6,3 @@ const { isUndefined } = require('../lib'); | ||
* Returns proper context ref for a given trigger id. | ||
* @param {ExecutionContext} parentContext - The parent context triggered the init | ||
* @param {ExecutionContextNode} parentContext - The parent context triggered the init | ||
* @param {Number} triggerAsyncId - The current triggerAsyncId | ||
@@ -29,23 +29,20 @@ */ | ||
const init = (executionContextMap) => (asyncId, type, triggerAsyncId) => { | ||
const parentContext = executionContextMap.get(triggerAsyncId); | ||
const parentContext = executionContextMap.get(triggerAsyncId); | ||
if (!parentContext || EXCLUDED_ASYNC_TYPES.has(type)) return; | ||
if (!parentContext || EXCLUDED_ASYNC_TYPES.has(type)) return; | ||
const ref = getContextRef(parentContext, triggerAsyncId); | ||
const ref = getContextRef(parentContext, triggerAsyncId); | ||
const refContext = executionContextMap.get(ref); | ||
// Setting child process entry as ref to parent context | ||
executionContextMap.set(asyncId, { | ||
ref, | ||
type, | ||
created: Date.now() | ||
}); | ||
// Setting child process entry as ref to parent context | ||
executionContextMap.set(asyncId, { | ||
ref, | ||
...(refContext.monitor && { | ||
created: Date.now(), | ||
type | ||
}) | ||
}); | ||
const { context = {}, children = [], ...meta } = executionContextMap.get(ref); | ||
// Adding current async as child to parent context in order to control cleanup better | ||
executionContextMap.set(ref, { | ||
...meta, | ||
context, | ||
children: [...children, asyncId] | ||
}); | ||
// Adding current async as child to parent context in order to control cleanup better | ||
refContext.children ? refContext.children.push(asyncId) : refContext.children = [asyncId]; | ||
}; | ||
@@ -61,7 +58,9 @@ | ||
const onChildProcessDestroy = (executionContextMap, asyncId, ref) => { | ||
const { children: parentChildren, context, ...meta } = executionContextMap.get(ref); | ||
const children = parentChildren.filter((id) => id !== asyncId); | ||
if (!executionContextMap.has(ref)) return; | ||
const refContext = executionContextMap.get(ref); | ||
const filtered = refContext.children.filter((id) => id !== asyncId); | ||
// Parent context will be released upon last child removal | ||
if (!children.length) { | ||
if (!filtered.length) { | ||
suspend(() => executionContextMap.delete(ref)); | ||
@@ -72,7 +71,3 @@ | ||
executionContextMap.set(ref, { | ||
...meta, | ||
context, | ||
children | ||
}); | ||
refContext.children = filtered; | ||
}; | ||
@@ -85,4 +80,4 @@ | ||
*/ | ||
const destroy = (executionContextMap) => (asyncId)=> { | ||
if (!executionContextMap.has(asyncId)) { return; } | ||
const destroy = (executionContextMap) => (asyncId) => { | ||
if (!executionContextMap.has(asyncId)) return; | ||
@@ -92,3 +87,5 @@ const { children = [], ref } = executionContextMap.get(asyncId); | ||
// As long as any root process holds none finished child process, we keep it alive | ||
if (children.length) { return; } | ||
if (children.length) { | ||
return; | ||
} | ||
@@ -108,3 +105,2 @@ // Child context's will unregister themselves from root context | ||
/** | ||
@@ -122,2 +118,2 @@ * The Create hooks callback to be passed to "async_hooks" | ||
module.exports = { create }; | ||
module.exports = { create, onChildProcessDestroy }; |
126
src/index.js
@@ -1,126 +0,6 @@ | ||
const asyncHooks = require('async_hooks'); | ||
const ExecutionContextResource = require('./lib/ExecutionContextResource'); | ||
const { isProduction, monitorMap } = require('./lib'); | ||
const { create: createHooks } = require('./hooks'); | ||
const { ExecutionContextErrors } = require('./constants'); | ||
const ExecutionContext = require('./ExecutionContext'); | ||
/** | ||
* Handles execution context error, throws when none production | ||
* @param code | ||
*/ | ||
const handleError = (code) => { | ||
if (!isProduction()) { | ||
throw code; | ||
} | ||
// Ensures only 1 instance exists per runtime. | ||
global.ExecutionContext = global.ExecutionContext || new ExecutionContext(); | ||
console.warn(code); | ||
}; | ||
/** | ||
* The Execution Context API | ||
* @return {ExecutionContextAPI} | ||
*/ | ||
const createExecutionContext = () => { | ||
/** | ||
* The global service context execution map | ||
* @type ExecutionContextMap | ||
*/ | ||
const executionContextMap = new Map(); | ||
// Sets node async hooks setup | ||
asyncHooks.createHook( | ||
createHooks(executionContextMap) | ||
).enable(); | ||
const Context = { | ||
/** | ||
* Creates an execution context for the current asyncId process. | ||
* This will expose Context get / update at any point after. | ||
* @param {Object} initialContext - The initial context to be used | ||
* @returns void | ||
*/ | ||
create: (initialContext = {}) => { | ||
const asyncId = asyncHooks.executionAsyncId(); | ||
// Creation is allowed once per execution context | ||
if (executionContextMap.has(asyncId)) handleError(ExecutionContextErrors.CONTEXT_ALREADY_DECLARED); | ||
executionContextMap.set(asyncId, { | ||
asyncId, | ||
context: { ...initialContext, executionId: asyncId }, | ||
created: Date.now(), | ||
children: [] | ||
}); | ||
}, | ||
/** | ||
* Updates the current async process context. | ||
* @param {Object} update - The update to apply on the current process context. | ||
* @returns void | ||
*/ | ||
update: (update = {}) => { | ||
const asyncId = asyncHooks.executionAsyncId(); | ||
if (!executionContextMap.has(asyncId)) handleError(ExecutionContextErrors.CONTEXT_DOES_NOT_EXISTS); | ||
const contextData = executionContextMap.get(asyncId); | ||
// Update target is always the root context, ref updates will need to be channeled | ||
const targetContextData = contextData.ref | ||
? executionContextMap.get(contextData.ref) | ||
: contextData; | ||
targetContextData.context = { ...targetContextData.context, ...update }; | ||
}, | ||
/** | ||
* Gets the current async process execution context. | ||
* @returns {Object} | ||
*/ | ||
get: () => { | ||
const asyncId = asyncHooks.executionAsyncId(); | ||
if (!executionContextMap.has(asyncId)) handleError(ExecutionContextErrors.CONTEXT_DOES_NOT_EXISTS); | ||
const { context = {}, ref } = executionContextMap.get(asyncId); | ||
if (ref) { | ||
// Ref will be used to point out on the root context | ||
return executionContextMap.get(ref).context; | ||
} | ||
// Root context | ||
return context; | ||
}, | ||
/** | ||
* Runs a given function within "AsyncResource" context, this will ensure the function executed within a uniq execution context. | ||
* @param {Function} fn - The function to run. | ||
* @param {Object} initialContext - The initial context to expose to the function execution | ||
*/ | ||
run: (fn, initialContext) => { | ||
const resource = new ExecutionContextResource(); | ||
resource.runInAsyncScope(() => { | ||
Context.create(initialContext); | ||
fn(); | ||
}); | ||
}, | ||
/** | ||
* Monitors current execution map usage | ||
* @return {ExecutionMapUsage} | ||
*/ | ||
monitor: () => { | ||
return monitorMap(executionContextMap); | ||
} | ||
}; | ||
return Context; | ||
}; | ||
global.ExecutionContext = global.ExecutionContext || createExecutionContext(); | ||
module.exports = global.ExecutionContext; |
@@ -0,1 +1,3 @@ | ||
const ExecutionContextResource = require('./ExecutionContextResource'); | ||
/** | ||
@@ -18,6 +20,17 @@ * The production environment | ||
module.exports = { | ||
ExecutionContextResource, | ||
env, | ||
/** | ||
* Checks if current environment matches production. | ||
* @param {String} environment - The current environment. | ||
* @return {Boolean} | ||
*/ | ||
isProduction: (environment = env) => environment === PRODUCTION, | ||
/** | ||
* Checks if a given value is undefined. | ||
* @param {String} thing that thing to check. | ||
* @return {Boolean} | ||
*/ | ||
isUndefined: (thing) => [null, undefined].includes(thing), | ||
@@ -27,3 +40,3 @@ | ||
* Returns a monitoring report over the "executionContext" memory usage. | ||
* @param {ExecutionContextMap} executionContextMap The execution map to monitor | ||
* @param {ExecutionContextMap} executionContextMap The execution map to monitor. | ||
* @return {ExecutionMapUsage} | ||
@@ -35,5 +48,6 @@ */ | ||
.filter(({ children }) => !!children) | ||
.map(({ asyncId, created, children, context = {} }) => ({ | ||
.map(({ asyncId, created, children, domain, context = {} }) => ({ | ||
asyncId, | ||
created, | ||
domain, | ||
contextSize: JSON.stringify(context).length, | ||
@@ -40,0 +54,0 @@ duration: getDuration(now, created), |
const lib = require ('.'); | ||
const { ExecutionContextErrors } = require('../ExecutionContext/constants'); | ||
const Context = require('../'); | ||
@@ -38,5 +39,13 @@ | ||
describe('monitorMap', () => { | ||
describe('When context is not configured to tracking', () => { | ||
it('Throws miss configured error', () => { | ||
expect(() => Context.monitor()).toThrow(ExecutionContextErrors.MONITOR_MISS_CONFIGURATION); | ||
}); | ||
}); | ||
describe('When no context is open', () => { | ||
let report; | ||
beforeEach(() => { | ||
Context.configure({ monitor: true }); | ||
report = Context.monitor(); | ||
@@ -43,0 +52,0 @@ }); |
@@ -1,91 +0,3 @@ | ||
interface ExecutionContext { | ||
ref? :number; | ||
children?: number[]; | ||
context?: object; | ||
} | ||
type ExecutionContextMap = Map<number, ExecutionContext>; | ||
interface HookCallbacks { | ||
/** | ||
* Called when a class is constructed that has the possibility to emit an asynchronous event. | ||
* @param asyncId a unique ID for the async resource | ||
* @param type the type of the async resource | ||
* @param triggerAsyncId the unique ID of the async resource in whose execution context this async resource was created | ||
* @param resource reference to the resource representing the async operation, needs to be released during destroy | ||
*/ | ||
init?(asyncId: number, type: string, triggerAsyncId: number, resource: object): void; | ||
/** | ||
* Called when a promise has resolve() called. This may not be in the same execution id | ||
* as the promise itself. | ||
* @param asyncId the unique id for the promise that was resolve()d. | ||
*/ | ||
promiseResolve?(asyncId: number): void; | ||
/** | ||
* Called after the resource corresponding to asyncId is destroyed | ||
* @param asyncId a unique ID for the async resource | ||
*/ | ||
destroy?(asyncId: number): void; | ||
} | ||
interface ExecutionMapUsageBaseEntry { | ||
asyncId: number; | ||
created: number; | ||
duration: number; | ||
} | ||
interface ExecutionMapUsageChildEntry extends ExecutionMapUsageBaseEntry { | ||
type: string; | ||
} | ||
interface ExecutionMapUsageEntry extends ExecutionMapUsageBaseEntry { | ||
asyncId: number; | ||
children: ExecutionMapUsageChildEntry[]; | ||
} | ||
interface ExecutionMapUsage { | ||
size: number; | ||
entries: ExecutionMapUsageEntry[]; | ||
} | ||
interface ExecutionContextAPI { | ||
/** | ||
* Creates an execution context for the current asyncId process. | ||
* @param initialContext | ||
*/ | ||
create(initialContext: object): void; | ||
/** | ||
* Updates the current async process context. | ||
* @param update | ||
*/ | ||
update(update: object): void; | ||
/** | ||
* Gets the current async process execution context. | ||
*/ | ||
get(): object; | ||
/** | ||
* Runs a given function within an async resource context | ||
* @param fn | ||
* @param initialContext | ||
*/ | ||
run(fn: Function, initialContext: object): void; | ||
/** | ||
* Monitors the current execution map usage | ||
*/ | ||
monitor(): ExecutionMapUsage; | ||
} | ||
export { | ||
ExecutionContextMap, | ||
ExecutionContextAPI, | ||
ExecutionMapUsage, | ||
ExecutionMapUsageEntry | ||
} | ||
export * from './lib/types'; | ||
export * from './hooks/types'; | ||
export * from './ExecutionContext/types'; |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
95642
155.18%34
25.93%1493
81.41%145
15.08%1
Infinity%7
40%1
Infinity%