@ehsc/cls-context-wrapper
v1.3.0
Published
A wrapper of the cls-hooked and node:async_hooks libs to use as middleware
Downloads
500
Maintainers
Readme
cls-context-wrapper (Typescript)
Continuous-Local-Storage Context Wrapper.
This is a Wrapper of the cls-hooked library (fixes included!) and AsyncLocalStorage.
The ContextWrapper class is a singleton instance that uses the cls-hooked or AsyncLocalStorage instance as its own Store.
This wrap is an easy plugin for web application libraries (middleware use) and other types of Node usage like service jobs, lambdas and different types of projects.
Example:
// App
import * as http from 'http';
import * as express from 'express';
import authentication from './middlewares/authentication';
import ContextWrapper from '@ehsc/cls-context-wrapper';
const app = express();
app.use(ContextWrapper.middleware);
app.use(authentication);
app.use((req, res, next) => {
ContextWrapper.setUserSession(req.user || { id: 1, user: 'ecardoso' });
ContextWrapper.set({ organization: 'EHSC' });
const instance = ContextWrapper.getInstance();
if (instance) instance.set({ foo: 'bar' });
next();
});
app.get('/test', (_, res) => {
res.json({
reqId: ContextWrapper.getRequestId(),
user: ContextWrapper.getUserSession(),
organization: ContextWrapper.get('organization'),
foo: ContextWrapper.getInstance().get('foo')
});
});
const server = app.listen(8000, () => {
ContextWrapper.getInstance({ name: 'MyApp', options: { correlationId: { enable: true } } });
});
// Request
http.get('http://localhost:8000/test', (res) => {
server.close();
let data: string = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('error', (err) => {
console.error('error:', err);
});
res.on('close', () => {
console.log('data:', JSON.parse(data));
});
});
If you have a specific architecture or problem to solve, you can:
const { default: ContextWrapper } = require('@ehsc/cls-context-wrapper');
// Sync
(() => {
const instance = ContextWrapper.getInstance({ name: 'MyApp' });
instance.run(() => {
ContextWrapper.set({ foo: 'bar' });
// or
instance.set({ foo: 'bar' });
// some inner function...
function doSomething() {
console.log(ContextWrapper.get('foo'));
}
doSomething(); // print "bar"
});
// Warning - Inner Contexts!!!
instance.run(() => {
ContextWrapper.set({ foo: 'bar' });
// or
instance.set({ foo: 'bar' });
instance.run(() => {
ContextWrapper.set({ john: 'doe' });
// some inner function...
function doSomething() {
console.log(ContextWrapper.get('foo'));
}
doSomething(); // print "bar"
console.log(ContextWrapper.get('john')); // print "doe"
});
console.log(ContextWrapper.get('john')); // print "undefined"
});
})();
// Async
(() => {
const instance = ContextWrapper.getInstance({ name: 'MyApp' });
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
instance.runPromise(async () => {
ContextWrapper.set({ start: new Date() });
await sleep(7000);
const end = new Date();
console.log('response time (ms):', Math.abs(end - ContextWrapper.get('start'))); // print "response time (ms): 700*"
});
})();
Note: The Context class is also available for use in other scenarios.
ContextWrapper Methods
static getInstance(params?: { name: string; options: object }): IContextStrategy
: create a singleton instance.params?.name: string
: name of the instance context.params?.mode: 'legacy'|'modern'
: you can choose modern or legacy instance. If you don't want to choose, the lib will check the node version. 16.17.0 or lower will choose the legacy instance. The 'modern' instance only works from node version 12.20 (experimental / unstable).params?.options?: object
: options.params?.options?.correlationId?: object
: correlationId config. It is used to store a unique id per request.params?.options?.correlationId?.enable: boolean
: to enable automatic set of correlationId inmiddleware
method. Default: true (if not passed params in the getInstance method in instance). Default value:true
.params?.options?.correlationId?.valuePath: string?
: the path if a correlationId already exists in thereq
middleware param, e.g.: 'reqId' or 'headers.reqId'. (only available if middleware method is used). If the valuePath is not passed, themiddleware
method will try to fetch fromheaders['Correlation-ID']
,headers['Request-ID']
orrequestId
(respectively). Default value: uuid.v4.params?.options?.trackingFlowId?: object
: trackingFlowId config. It is used to store the id of an entire flow (e.g.: business flow), ex: Signup (All steps). As it is returned in the http response headers, it can be written to localstorage or any other storage and be used in all requests of an entire flow (e.g.: business logic flow).params?.options?.trackingFlowId?.enable: boolean
: to enable automatic set of trackingFlowId inmiddleware
method. Default: true (if not passed params in the getInstance method in instance). Default value:false
.params?.options?.trackingFlowId?.valuePath: string?
: the path if a trackingFlowId already exists in thereq
middleware param, e.g.: 'reqsssssssssId' or 'headers.reqId'. (only available if middleware method is used). If the valuePath is not passed, themiddleware
method will try to fetch fromheaders['Tracking-Flow-ID']
ortrackingFlowId
(respectively). Default value:undefined
'.Returns an instance that implements the
IContextStrategy
interface, a super set of the AsyncLocalStorage from node:async_hooks(if the Node version is 14.20.0 or major) or Namespace from cls-hooked lib.IContextStrategy
Methods:destroy(): void
: whenever you are going to delete, remove or no longer use the Instance, call destroy to remove the instance context. IfgetInstance
is called afterdestroy
, will be created a new instance.run(callback: () => void): void
: Runs a function synchronously within a context and returns its return value. The storage is not accessible outside of the callback function. The store is accessible to any asynchronous operations created within the callback.runPromise(callback: () => Promise<void>): void
: Runs a function synchronously within a context and returns its return value. The storage is not accessible outside of the callback function. The store is accessible to any asynchronous operations created within the async callback.set(store: { [prop: string]: any }): void
: set a value in the context.get(key?: string): any
: retrieve a value from the context, if key not passed, it retrieve whole object from the context.use(req: Request, res: Response, next: () => void): void
: Use like a middleware (express, koa, etc.).
static destroy(): void
: whenever you are going to delete, remove or no longer use the Instance, call destroy to remove the instance context. IfgetInstance
is called afterdestroy
, will be created a new instance.
static set(store: { [prop: string]: any }): void
: set a key value in the context.
static get(key?: string): any
: read a value previously recorded.
static setCorrelationId(value: string | number): void
: set the correlation identifier, like ContextWrapper.set({ correlationId: uuid.v4() }) (see pattern).
static getCorrelationId(): string | number | undefined
: retrieve correlation identifier value, like ContextWrapper.get('correlationId').
static setTrackingFlowId(value: string | number): void
: set the tracking flow identifier, like ContextWrapper.set({ trackingFlowId: uuid.v4() }).
static getTrackingFlowId(): string | number | undefined
: retrieve tracking flow identifier value, like ContextWrapper.get('trackingFlowId').
static setUserSession(value: { [prop: string]: any } | any): void
: set user, like ContextWrapper.set({ user: {...} }).
static getUserSession(): { [prop: string]: any } | any
: get user, like ContextWrapper.get('user').
middleware(): void
: all of the middlewares and routes setup can set or read context values if it is called after this middleware, like asyncLocalStorage.run and Namespace.run.
Notes:
ContextWrapper.middleware
will only create a request context ifContextWrapper.getInstance
is called before.- Methods
set
,get
,set/get CorrelationId
,set/get UserSession
only works ifinstance.run
,instance.runPromise
orContextWrapper.middleware
is called before.
License
This project is distributed under the MIT license.
For contact, feel free to email me: [email protected].
ps: sorry for any english mistakes. :)
Enjoy it!