UNPKG

@nestjs-steroids/async-context

Version:

NestJS Async Context based on async_hooks

100 lines 7.84 kB
{ "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" }