diff --git a/packages/nest/README.md b/packages/nest/README.md index 44d4b8770..c6770aeec 100644 --- a/packages/nest/README.md +++ b/packages/nest/README.md @@ -7,7 +7,7 @@

-

OpenFeature Nest.js SDK

+

OpenFeature NestJS SDK

@@ -15,7 +15,11 @@ Specification -
+ + + Release + + codecov @@ -25,11 +29,49 @@ [OpenFeature](https://openfeature.dev) is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool. -๐Ÿงช This SDK is experimental. + + +## Overview + +The OpenFeature NestJS SDK is a package that provides a NestJS wrapper for the [OpenFeature Server SDK](https://openfeature.dev/docs/reference/technologies/server/javascript/). + +Capabilities include: + +- Provide a NestJS global module to simplify OpenFeature configuration and usage within NestJS; +- Injecting feature flags directly into controller route handlers by using decorators; +- Injecting transaction evaluation context for flag evaluations directly from [execution context](https://docs.nestjs.com/fundamentals/execution-context) (HTTP header values, client IPs, etc.); +- Injecting OpenFeature clients into NestJS services and controllers by using decorators; +- Setting up logging, event handling, hooks and providers directly when registering the module. + +## ๐Ÿš€ Quick start + +### Requirements + +- Node.js version 16+ +- NestJS version 8+ + +### Install + +#### npm + +```sh +npm install --save @openfeature/nestjs-sdk +``` + +#### Required peer dependencies -#### Here's a basic example of how to use the OpenFeature NestJS API with `InMemoryProvider`. +The following list contains the peer dependencies of `@openfeature/nestjs-sdk` with it's expected and compatible versions: -#### Registering the Nest.js SDK module in the App Module: +* `@openfeature/server-sdk`: >=1.7.5 +* `@nestjs/common`: ^8.0.0 || ^9.0.0 || ^10.0.0 +* `@nestjs/core`: ^8.0.0 || ^9.0.0 || ^10.0.0 +* `rxjs`: ^6.0.0 || ^7.0.0 || ^8.0.0 + +The minimum required version of `@openfeature/server-sdk` currently is `1.7.5`. + +### Usage + +The example below shows how to use the `OpenFeatureModule` with OpenFeature's `InMemoryProvider`. ```ts import { Module } from '@nestjs/common'; @@ -44,24 +86,19 @@ import { InMemoryProvider } from '@openfeature/web-sdk'; testBooleanFlag: { defaultVariant: 'default', variants: { default: true }, - disabled: false + disabled: false, }, - companyName: { - defaultVariant: 'default', - variants: { default: "BigCorp" }, - disabled: false - } }), providers: { - differentProvider: new InMemoryProvider() - } - }) - ] + differentProvider: new InMemoryProvider(), + }, + }), + ], }) export class AppModule {} ``` -#### Injecting a feature flag with header value in evaluation context into an endpoint handler method +With the `OpenFeatureModule` configured, it's possible to inject flag evaluation details into route handlers like in the following code snippet. ```ts import { Controller, ExecutionContext, Get } from '@nestjs/common'; @@ -70,19 +107,6 @@ import { BooleanFeatureFlag } from '@openfeature/nestjs-sdk'; import { EvaluationDetails } from '@openfeature/server-sdk'; import { Request } from 'express'; -function getContext(executionContext: ExecutionContext) { - const request = executionContext.switchToHttp().getRequest(); - const userId = request.header('x-user-id'); - - if (!userId) { - return undefined; - } - - return { - targetingKey: userId, - }; -} - @Controller() export class OpenFeatureController { @Get('/welcome') @@ -90,20 +114,19 @@ export class OpenFeatureController { @BooleanFeatureFlag({ flagKey: 'testBooleanFlag', defaultValue: false, - contextFactory: getContext, }) - feature: Observable>, + feature: Observable>, ) { return feature.pipe( map((details) => - details.value ? 'Welcome to this OpenFeature-enabled Nest.js app!' : 'Welcome to this Nest.js app!', + details.value ? 'Welcome to this OpenFeature-enabled NestJS app!' : 'Welcome to this NestJS app!', ), ); } } ``` -#### Injecting the default and a named client into a service: +It is also possible to inject the default or named OpenFeature clients into a service via Nest dependency injection system. ```ts import { Injectable } from '@nestjs/common'; @@ -114,15 +137,19 @@ import { FeatureClient } from '@openfeature/nestjs-sdk'; export class OpenFeatureTestService { constructor( @FeatureClient() private defaultClient: Client, - @FeatureClient({ name: 'differentServer' }) private namedClient: Client, - ) { - } + @FeatureClient({ name: 'differentProvider' }) private namedClient: Client, + ) {} - public async getMessage() { - const companyName = await this.defaultClient.getStringValue('companyName', 'Unknown Company'); - return `Hey User from ${companyName}`; + public async getBoolean() { + return await this.defaultClient.getBooleanValue('testBooleanFlag', false); } } ``` +## Module aditional information + +### Flag evaluation context injection +Whenever a flag evaluation occurs, context can be provided with information like user e-mail, role, targeting key, etc in order to trigger specific evaluation rules or logic. The `OpenFeatureModule` provides a way to configure context for each request using the `contextFactory` option. +The `contextFactory` is ran in a NestJS interceptor scope to configure the evaluation context and than it is used in every flag evaluation related to this request. +By default, the interceptor is configured globally, but it can be changed by setting the `useGlobalInterceptor` to `false`. In this case, it is still possible to configure a `contextFactory` that can be injected into route, module or controller bound interceptors. diff --git a/packages/nest/src/evaluation-context-interceptor.ts b/packages/nest/src/evaluation-context-interceptor.ts index 154e39ce4..5ec04d674 100644 --- a/packages/nest/src/evaluation-context-interceptor.ts +++ b/packages/nest/src/evaluation-context-interceptor.ts @@ -2,7 +2,31 @@ import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } fr import { ContextFactory, ContextFactoryToken } from './context-factory'; import { Observable } from 'rxjs'; import { OpenFeature } from '@openfeature/server-sdk'; +import { OpenFeatureModule } from './open-feature.module'; +/** + * NestJS interceptor used in {@link OpenFeatureModule} + * to configure flag evaluation context. + * + * This interceptor is configured globally by default. + * If `useGlobalInterceptor` is set to `false` in {@link OpenFeatureModule} it needs to be configured for the specific controllers or routes. + * + * If just the interceptor class is passed to the `UseInterceptors` like below, the `contextFactory` provided in the {@link OpenFeatureModule} will be injected and used in order to create the context. + * ```ts + * //route interceptor + * @UseInterceptors(EvaluationContextInterceptor) + * @Get('/user-info') + * getUserInfo(){} + * ``` + * + * A different `contextFactory` can also be provided, but the interceptor instance has to be instantiated like in the following example. + * ```ts + * //route interceptor + * @UseInterceptors(new EvaluationContextInterceptor()) + * @Get('/user-info') + * getUserInfo(){} + * ``` + */ @Injectable() export class EvaluationContextInterceptor implements NestInterceptor { constructor(@Inject(ContextFactoryToken) private contextFactory?: ContextFactory) {} diff --git a/packages/nest/src/evaluation-context-propagator.ts b/packages/nest/src/evaluation-context-propagator.ts index f979e1450..8a063639a 100644 --- a/packages/nest/src/evaluation-context-propagator.ts +++ b/packages/nest/src/evaluation-context-propagator.ts @@ -1,6 +1,5 @@ -import { TransactionContextPropagator } from '@openfeature/server-sdk'; +import { TransactionContextPropagator, EvaluationContext } from '@openfeature/server-sdk'; import { AsyncLocalStorage } from 'async_hooks'; -import { EvaluationContext } from '@openfeature/server-sdk'; export class AsyncLocalStorageTransactionContext implements TransactionContextPropagator { private asyncLocalStorage = new AsyncLocalStorage(); diff --git a/packages/nest/src/open-feature.module.ts b/packages/nest/src/open-feature.module.ts index 6f1aa5ded..2a0af1cf2 100644 --- a/packages/nest/src/open-feature.module.ts +++ b/packages/nest/src/open-feature.module.ts @@ -23,6 +23,9 @@ import { AsyncLocalStorageTransactionContext } from './evaluation-context-propag import { EvaluationContextInterceptor } from './evaluation-context-interceptor'; import { ShutdownService } from './shutdown.service'; +/** + * OpenFeatureModule is a NestJS wrapper for OpenFeature Server-SDK. + */ @Module({}) export class OpenFeatureModule { static forRoot({ useGlobalInterceptor = true, ...options }: OpenFeatureModuleOptions): DynamicModule {