Skip to content

Commit

Permalink
Feature: Reuse existing context (#58)
Browse files Browse the repository at this point in the history
* feat: add context inheritance options

* feat: add setIfUndefined function

* fix: reuse context in GQL interceptors

* docs: update docs
  • Loading branch information
Papooch authored May 26, 2023
1 parent 2fb6d50 commit 63086bf
Show file tree
Hide file tree
Showing 21 changed files with 21,680 additions and 21,492 deletions.
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"editor.formatOnSave": true,
"typescript.preferences.importModuleSpecifier": "relative"
}
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.tsdk": "node_modules/typescript/lib"
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NestJS CLS (Async Context)

A continuation-local\* storage module compatible with [NestJS](https://nestjs.com/)' dependency injection based on [AsyncLocalStorage](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage).
A continuation-local storage module compatible with [NestJS](https://nestjs.com/)' dependency injection based on [AsyncLocalStorage](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage).

> **Notice**: The documentation has been moved to [a dedicated website](https://papooch.github.io/nestjs-cls/).
Expand Down
48 changes: 35 additions & 13 deletions docs/docs/04_api/01_service-interface.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
# Service Interface

## ClsService

The injectable `ClsService` provides the following API to manipulate the cls context:

The `S` type parameter is used as the type of custom `ClsStore`.

- **_`set`_**`(key: keyof S, value: S[key]): void`
Set a value on the CLS context.

- **_`get`_**`(): S`
Get the entire CLS context.

- **_`get`_**`(key?: keyof S): S[key]`
Retrieve a value from the CLS context by key.

- **_`getId`_**`(): string;`
Retrieve the request ID (a shorthand for `cls.get(CLS_ID)`)

- **_`has`_**`(key: keyof S): boolean`
Check if a key is in the CLS context.

- **_`getId`_**`(): string;`
Retrieve the request ID (a shorthand for `cls.get(CLS_ID)`)
- **_`set`_**`(key: keyof S, value: S[key]): void`
Set a value on the CLS context.

- **_`setIfUndefined`_**`(key: keyof S, value: S[key]): void`
Set a value on the CLS context _only_ if it hasn't been already set. Useful for ensuring idempotence if you have multiple entry points.

- **_`run`_**`(callback: () => T): T`
**_`run`_**`(options: ClsContextOptions, callback: () => T): T;`
Run the callback in a shared CLS context. Optionally takes an [options object](#clscontextoptions) as the first parameter.

- **_`runWith`_**`(store: S, callback: () => T): T`
Run the callback in a new CLS context (while supplying the default store).

- **_`enter`_**`(): void;`
Run any following code in a shared CLS context.
**_`enter`_**`(options: ClsContextOptions): void`
Run any following code in a shared CLS context. Optionally takes an [options object](#clscontextoptions) as the first parameter.

- **_`enterWith`_**`(store: S): void;`
- **_`enterWith`_**`(store: S): void`
Run any following code in a new CLS context (while supplying the default store).

- **_`run`_**`(callback: () => T): T;`
Run the callback in a shared CLS context.

- **_`runWith`_**`(store: S, callback: () => T): T;`
Run the callback in a new CLS context (while supplying the default store).
- **_`exit`_**`(callback: () => T): T`
Run the callback _without_ access to a shared CLS context.

- **_`isActive`_**`(): boolean`
Whether the current code runs within an active CLS context.

- **_`resolveProxyProviders`_**`(): Promise<void>`
Manually trigger resolution of Proxy Providers.
Manually trigger resolution of Proxy Providers.

## ClsContextOptions

The `run` and `enter` methods can take an additional options object with the following settings:

- **_`ifNested?:`_** `'override' | 'inherit' | 'reuse'`
Sets the behavior of nested CLS context creation in case the method is invoked in an existing context. It has no effect if no parent context exist.
- `override` (default) - Run the callback with an new empty context.
No values from the parent context will be accessible within the wrapped code.
- `inherit` - Run the callback with a shallow copy of the parent context.
Re-assignments of top-level properties will not be reflected in the parent context. However, modifications of existing properties _will_ be reflected.
- `reuse` - Reuse existing context without creating a new one. All modifications to the existing context will be reflected.
6 changes: 5 additions & 1 deletion docs/docs/05_considerations/01_security.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ The `ClsGuard` (and `ClsMiddleware`, if configured so) uses the less safe `enter

**This has a consequence that should be taken into account:**

> When the `enterWith` method is used, any consequent requests _get access_ to the context of the previous one _until the request hits the `enterWith` call_.
:::caution

When the `enterWith` method is used, any consequent requests _get access_ to the CLS context of the previous request _until the request hits the `enterWith` call_.

:::

That means, when using `ClsMiddleware` with the `useEnterWith` option, or `ClsGuard` to set up context, be sure to mount them as early in the request lifetime as possible and do not use any other enhancers that rely on `ClsService` before them. For `ClsGuard`, that means you should probably manually mount it in `AppModule` if you require any other guard to run _after_ it.

Expand Down
8 changes: 5 additions & 3 deletions docs/docs/05_considerations/02_compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ Known issues:

## GraphQL

Using an interceptor or a guard may result in that enhancer triggering multiple times in case there are multiple queries in the GQL request. Due to this, you should ensure that any operation on the CLS store within enhancers is _idempotent_. This includes the `setup` function.
Using an interceptor or a guard may result in that enhancer triggering multiple times in case there are multiple queries in the GQL request.

Due to this, you should ensure that any operation on the CLS store within enhancers is _idempotent_. This includes the `setup` function. Therefore, it is advised to use the `ClsService#setIfUndefined()` method.

Tested with:

- ✔ Apollo (Express)
- ✔ Mercurius (Fastify)

### `@nestjs/graphql >= 10`,
### `@nestjs/graphql >= 10`

Since v10, this package is compatible with GraphQL resolvers and the preferred way is to use the `ClsMiddleware` with the `mount` option.
Since v10, Nest's GraphQL resolvers are compatible with this package and the preferred way to initialize the CLS context is use the `ClsMiddleware` with the `mount` option.

### `@nestjs/graphql < 10`

Expand Down
Loading

0 comments on commit 63086bf

Please sign in to comment.