@nestjs-steroids/async-context
Version:
NestJS Async Context based on async_hooks
100 lines • 7.84 kB
JSON
{
"name": "@nestjs-steroids/async-context",
"version": "2.0.0",
"description": "NestJS Async Context based on async_hooks",
"author": "Vlad Krokhin",
"private": false,
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/nestjs-steroids/async-context"
},
"bugs": {
"url": "https://github.com/nestjs-steroids/async-context/issues"
},
"keywords": [
"nest",
"nestjs",
"async-context",
"async-hooks",
"async_hooks",
"async-hooks-context",
"async-local-storage"
],
"files": [
"dist"
],
"main": "dist/index.js",
"engines": {
"node": ">=12.17.0"
},
"peerDependencies": {
"@nestjs/common": ">=7.0.0",
"@nestjs/core": ">=7.0.0"
},
"devDependencies": {
"@nestjs/cli": "^8.2.0",
"@nestjs/common": "^8.2.6",
"@nestjs/core": "^8.2.6",
"@nestjs/schematics": "^8.0.5",
"@types/jest": "27.4.0",
"@types/node": "^16.11.7",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"eslint": "^7.32.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.2.0",
"jest": "27.4.7",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.2",
"ts-jest": "27.1.3",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"tsconfig-paths": "^3.12.0",
"typescript": "^4.5.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testPathIgnorePatterns": [
"<rootDir>/index.ts"
],
"testEnvironment": "node"
},
"eslintConfig": {
"extends": "standard-with-typescript",
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/no-extraneous-class": "off"
}
},
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc -p tsconfig.json",
"prepublish": "npm run build && rm dist/*.spec.*",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
},
"readme": "<h1 align=\"center\">Async Context</h1>\n\nZero-dependency module for NestJS that allow to track context between async call\n\n## Installation\n```bash\nnpm install @nestjs-steroids/async-context\nyarn add @nestjs-steroids/async-context\npnpm install @nestjs-steroids/async-context\n```\n\n## Usage\nThe first step is to register `AsyncContext` inside interceptor (or middleware)\n> `src/async-context.interceptor.ts`\n```typescript\nimport { randomUUID } from 'crypto'\nimport {\n Injectable,\n NestInterceptor,\n ExecutionContext,\n CallHandler\n} from '@nestjs/common'\nimport { AsyncContext } from '@nestjs-steroids/async-context'\nimport { Observable } from 'rxjs'\n\n@Injectable()\nexport class AsyncContextInterceptor implements NestInterceptor {\n constructor (private readonly ac: AsyncContext<string, any>) {}\n\n intercept (context: ExecutionContext, next: CallHandler): Observable<any> {\n this.ac.register() // Important to call .register or .registerCallback (good for middleware)\n this.ac.set('traceId', randomUUID()) // Setting default value traceId\n return next.handle()\n }\n}\n```\n\nThe second step is to register `AsyncContextModule` and interceptor inside main module\n> `src/app.module.ts`\n```typescript\nimport { APP_INTERCEPTOR } from '@nestjs/core';\nimport { Module } from '@nestjs/common';\nimport { AsyncContextModule } from '@nestjs-steroids/async-context';\nimport { AsyncContextInterceptor } from './async-context.interceptor';\n\n@Module({\n imports: [\n AsyncContextModule.forRoot()\n ],\n providers: [\n {\n provide: APP_INTERCEPTOR,\n useClass: AsyncContextInterceptor,\n },\n ],\n})\nexport class AppModule {}\n```\nThe last step is to inject `AsyncContext` inside controller or service and use it\n> ``src/app.controller.ts``\n```typescript\nimport { Controller, Get, Logger } from '@nestjs/common'\nimport { AppService } from './app.service'\nimport { AsyncContext } from '@nestjs-steroids/async-context'\n\n@Controller()\nexport class AppController {\n constructor (\n private readonly appService: AppService,\n private readonly asyncContext: AsyncContext<string, string>,\n private readonly logger: Logger\n ) {}\n\n @Get()\n getHello (): string {\n this.logger.log('AppController.getHello', this.asyncContext.get('traceId'))\n process.nextTick(() => {\n this.logger.log(\n 'AppController.getHello -> nextTick',\n this.asyncContext.get('traceId')\n )\n setTimeout(() => {\n this.logger.log(\n 'AppController.getHello -> nextTick -> setTimeout',\n this.asyncContext.get('traceId')\n )\n }, 0)\n })\n return this.appService.getHello()\n }\n}\n\n```\n\n## Output example\n\n```\n[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [NestFactory] Starting Nest application...\n[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [InstanceLoader] AsyncContextModule dependencies initialized +47ms\n[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [InstanceLoader] AppModule dependencies initialized +1ms\n[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [RoutesResolver] AppController {/}: +12ms\n[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [RouterExplorer] Mapped {/, GET} route +7ms\n[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [NestApplication] Nest application successfully started +5ms\n[Nest] 141168 - 02/01/2022, 11:33:13 PM LOG [7398d3ad-c246-4650-8dd0-f8f29238bdd7] AppController.getHello\n[Nest] 141168 - 02/01/2022, 11:33:13 PM LOG [7398d3ad-c246-4650-8dd0-f8f29238bdd7] AppController.getHello -> nextTick\n[Nest] 141168 - 02/01/2022, 11:33:13 PM LOG [7398d3ad-c246-4650-8dd0-f8f29238bdd7] AppController.getHello -> nextTick -> setTimeout\n```\n\n## API\n\n### `AsyncContext` almost identical to native `Map` object\n```typescript\nclass AsyncContext {\n // Clear all values from storage\n clear(): void;\n\n // Delete value by key from storage\n delete(key: K): boolean;\n\n // Iterate over storage\n forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;\n\n // Get value from storage by key\n get(key: K): V | undefined;\n\n // Check if key exists in storage\n has(key: K): boolean;\n\n // Set value by key in storage\n set(key: K, value: V): this;\n\n // Get number of keys that stored in storage\n get size: number;\n\n // Register context, it's better to use this method inside the interceptor\n register(): void\n\n // Register context for a callback, it's better to use this inside the middleware\n registerCallback<R, TArgs extends any[]>(callback: (...args: TArgs) => R, ...args: TArgs): R\n\n // Unregister context\n unregister(): void\n}\n\n```\n### `AsyncContextModule`\n```typescript\ninterface AsyncContextModuleOptions {\n // Should register this module as global, default: true\n isGlobal?: boolean\n\n // In case if you need to provide custom value AsyncLocalStorage\n alsInstance?: AsyncLocalStorage<any>\n}\n\nclass AsyncContextModule {\n static forRoot (options?: AsyncContextModuleOptions): DynamicModule\n}\n```\n\n## Migration guide from V1\nYou need to replace `AsyncHooksModule` by `AsyncContextModule.forRoot()`\n\n## License\n[MIT](LICENSE.md)\n"
}