Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/nestjs-steroids/async-context

:link: NestJS Async Context based on async_hooks
https://github.com/nestjs-steroids/async-context

async-context asynchooks asyncstorage nestjs nodejs request-context requestid

Last synced: 8 days ago
JSON representation

:link: NestJS Async Context based on async_hooks

Awesome Lists containing this project

README

        

Async Context

Zero-dependency module for NestJS that allow to track context between async call

## Installation
```bash
npm install @nestjs-steroids/async-context
yarn add @nestjs-steroids/async-context
pnpm install @nestjs-steroids/async-context
```

## Usage
The first step is to register `AsyncContext` inside interceptor (or middleware)
> `src/async-context.interceptor.ts`
```typescript
import { randomUUID } from 'crypto'
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler
} from '@nestjs/common'
import { AsyncContext } from '@nestjs-steroids/async-context'
import { Observable } from 'rxjs'

@Injectable()
export class AsyncContextInterceptor implements NestInterceptor {
constructor (private readonly ac: AsyncContext) {}

intercept (context: ExecutionContext, next: CallHandler): Observable {
this.ac.register() // Important to call .register or .registerCallback (good for middleware)
this.ac.set('traceId', randomUUID()) // Setting default value traceId
return next.handle()
}
}
```

The second step is to register `AsyncContextModule` and interceptor inside main module
> `src/app.module.ts`
```typescript
import { APP_INTERCEPTOR } from '@nestjs/core';
import { Module } from '@nestjs/common';
import { AsyncContextModule } from '@nestjs-steroids/async-context';
import { AsyncContextInterceptor } from './async-context.interceptor';

@Module({
imports: [
AsyncContextModule.forRoot()
],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: AsyncContextInterceptor,
},
],
})
export class AppModule {}
```
The last step is to inject `AsyncContext` inside controller or service and use it
> ``src/app.controller.ts``
```typescript
import { Controller, Get, Logger } from '@nestjs/common'
import { AppService } from './app.service'
import { AsyncContext } from '@nestjs-steroids/async-context'

@Controller()
export class AppController {
constructor (
private readonly appService: AppService,
private readonly asyncContext: AsyncContext,
private readonly logger: Logger
) {}

@Get()
getHello (): string {
this.logger.log('AppController.getHello', this.asyncContext.get('traceId'))
process.nextTick(() => {
this.logger.log(
'AppController.getHello -> nextTick',
this.asyncContext.get('traceId')
)
setTimeout(() => {
this.logger.log(
'AppController.getHello -> nextTick -> setTimeout',
this.asyncContext.get('traceId')
)
}, 0)
})
return this.appService.getHello()
}
}

```

## Output example

```
[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [NestFactory] Starting Nest application...
[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [InstanceLoader] AsyncContextModule dependencies initialized +47ms
[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [InstanceLoader] AppModule dependencies initialized +1ms
[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [RoutesResolver] AppController {/}: +12ms
[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [RouterExplorer] Mapped {/, GET} route +7ms
[Nest] 141168 - 02/01/2022, 11:33:11 PM LOG [NestApplication] Nest application successfully started +5ms
[Nest] 141168 - 02/01/2022, 11:33:13 PM LOG [7398d3ad-c246-4650-8dd0-f8f29238bdd7] AppController.getHello
[Nest] 141168 - 02/01/2022, 11:33:13 PM LOG [7398d3ad-c246-4650-8dd0-f8f29238bdd7] AppController.getHello -> nextTick
[Nest] 141168 - 02/01/2022, 11:33:13 PM LOG [7398d3ad-c246-4650-8dd0-f8f29238bdd7] AppController.getHello -> nextTick -> setTimeout
```

## API

### `AsyncContext` almost identical to native `Map` object
```typescript
class AsyncContext {
// Clear all values from storage
clear(): void;

// Delete value by key from storage
delete(key: K): boolean;

// Iterate over storage
forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void;

// Get value from storage by key
get(key: K): V | undefined;

// Check if key exists in storage
has(key: K): boolean;

// Set value by key in storage
set(key: K, value: V): this;

// Get number of keys that stored in storage
get size: number;

// Register context, it's better to use this method inside the interceptor
register(): void

// Register context for a callback, it's better to use this inside the middleware
registerCallback(callback: (...args: TArgs) => R, ...args: TArgs): R

// Unregister context
unregister(): void
}

```
### `AsyncContextModule`
```typescript
interface AsyncContextModuleOptions {
// Should register this module as global, default: true
isGlobal?: boolean

// In case if you need to provide custom value AsyncLocalStorage
alsInstance?: AsyncLocalStorage
}

class AsyncContextModule {
static forRoot (options?: AsyncContextModuleOptions): DynamicModule
}
```

## Migration guide from V1
You need to replace `AsyncHooksModule` by `AsyncContextModule.forRoot()`

## License
[MIT](LICENSE.md)