@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"
}