Coder Social home page Coder Social logo

nestjs-unleash's Introduction

NestJS-Unleash

Unleash module for NestJS

Table of contents

Setup

$ npm install --save nestjs-unleash

Import the module with UnleashModule.forRoot(...) or UnleashModule.forRootAsync(...).

Synchronous configuration

Use UnleashModule.forRoot(). Available options are described in the UnleashModuleOptions interface.

@Module({
  imports: [
    UnleashModule.forRoot({
      url: "https://example.com/unleash",
      appName: "my-app-name",
      instanceId: "my-unique-instance",
    }),
  ],
})
export class MyModule {}

Asynchronous configuration

If you want to use your Unleash options dynamically, use UnleashModule.forRootAsync(). Use useFactory and inject to import your dependencies. Example using the ConfigService:

@Module({
  imports: [
    UnleashModule.forRootAsync({
      useFactory: (config: ConfigService) => ({
        url: config.get("UNLEASH_URL"),
        appName: config.get("UNLEASH_APP_NAME"),
        instanceId: config.get("UNLEASH_INSTANCE_ID"),
        refreshInterval: config.get("UNLEASH_REFRESH_INTERVAL"),
        metricsInterval: config.get("UNLEASH_METRICS_INTERVAL"),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class MyModule {}

Usage in controllers or providers

In your controller use the UnleashService or the @IfEnabled(...) route decorator:

import { UnleashService } from "nestjs-unleash";

@Controller()
@UseGuards(UserGuard)
export class AppController {
  constructor(private readonly unleash: UnleashService) {}

  @Get("/")
  index(): string {
    // the UnleashService can be used in all controllerrs and provideers
    return this.unleash.isEnabled("test")
      ? "feature is active"
      : "feature is not active";
  }

  // Throws a NotFoundException if the feature is not enabled
  @IfEnabled("test")
  @Get("/foo")
  getFoo(): string {
    return "my foo";
  }
}

Custom context

The UnleashContext grants access to request related information like user ID or IP address.

In addition, the context can be dynamically enriched with further information and subsequently used in a separate strategy:

export interface MyCustomData {
  foo: string;
  bar: number;
}

@Injectable()
class SomeProvider {
  constructor(private readonly unleash: UnleashService<MyCustomData>) {}

  someMethod() {
    return this.unleash.isEnabled("someToggleName", undefined, {
      foo: "bar",
      bar: 123,
    })
      ? "feature is active"
      : "feature is not active";
  }
}

// Custom strategy with custom data:
@Injectable()
export class MyCustomStrategy implements UnleashStrategy {
  name = "MyCustomStrategy";

  isEnabled(
    _parameters: unknown,
    context: UnleashContext<MyCustomData>
  ): boolean {
    return context.customData?.foo === "bar";
  }
}

Configuration

NestJS-Unleash can be configured with the following options:

interface UnleashModuleOptions {
  /**
   * If "true", registers `UnleashModule` as a global module.
   * See: https://docs.nestjs.com/modules#global-modules
   *
   * @default true
   */
  global?: boolean;

  /**
   * URL of your Unleash server
   *
   * @example http://unleash.herokuapp.com/api/client
   */
  url: string;

  /**
   * Name of the application seen by unleash-server
   */
  appName: string;

  /**
   * Instance id for this application (typically hostname, podId or similar)
   */
  instanceId: string;

  /**
   * Additional options for the HTTP request to the Unleash server, e.g. custom
   * HTTP headers
   */
  http?: AxiosRequestConfig;

  /**
   * At which interval, in milliseconds, will this client update its feature
   * state
   */
  refreshInterval?: number;

  /**
   * At which interval, in milliseconds, will this client send metrics
   */
  metricsInterval?: number;

  /**
   * Array of custom strategies. These classes mus implement the `UnleashStrategy` interface.
   */
  strategies?: Type<UnleashStrategy>[];

  /**
   * `nestjs-unleash` sends an initial registration request to the unleash server at startup. This behavior can be disabled by this option.
   */
  disableRegistration?: boolean;

  /**
   * Some strategies depend on the user ID of the currently logged in user. The
   * user ID is expected by default in `request.user.id`. To customize this
   * behavior, a custom user ID factory can be provided.
   */
  userIdFactory?: (request: Request<{ id: string }>) => string;
}

Default strategies

This module supports the official standard activation strategies. They do not need to be activated separately and work out of the box.

Custom strategies

In order to create a custom strategy you have to create a class wich inplements the UnleashStrategy interface:

import { UnleashContext } from "nestjs-unleash";

export interface UnleashStrategy {
  /**
   * Must match the name you used to create the strategy in your Unleash
   * server UI
   */
  name: string;

  /**
   * Determines whether the feature toggle is active
   *
   * @param parameters Custom paramemters as configured in Unleash server UI
   * @param context applicaton/request context, i.e. UserID
   */
  isEnabled(parameters: unknown, context: UnleashContext): boolean;
}

Example custom strategy:

import { Injectable } from "@nestjs/common";
import { UnleashContext, UnleashStrategy } from "nestjs-unleash";

@Injectable()
export class MyCustomStrategy implements UnleashStrategy {
  name = "MyCustomStrategy";

  isEnabled(parameters: any, context: UnleashContext): boolean {
    return Math.random() < 0.5;
  }
}

Now you can use it your module setup as follows:

import { MyCustomStrategy } from "./my-custom-strategy";

@Module({
  imports: [
    UnleashModule.forRoot({
      // ...
      strategies: [MyCustomStrategy],
    }),
  ],
})
export class ApplicationModule {}

License

nestjs-unleash is distributed under the MIT license. See LICENSE for details.

nestjs-unleash's People

Contributors

danilolutz avatar diegorubin avatar pmb0 avatar renovate-bot avatar renovate[bot] avatar semantic-release-bot avatar simenandre avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nestjs-unleash's Issues

Support nestjs-proxy strategies

Since the way we write strategies in this package isn't like the regular client, we can't reuse our strategies in the proxy that is needed for anyone that has also a front-end/mobile app.

I've created a helper that allows to dynamically use the nestjs strategies in the regular proxy.

import { UnleashStrategy, UnleashContext } from 'nestjs-unleash';
import { Strategy, Context } from 'unleash-client';

export function ProxifyStrategy(type: { new (): UnleashStrategy }): Strategy {
  // do stuff to return new instance of T.
  return new ProxyStrategy(new type());
}

export class ProxyStrategy extends Strategy {
  /**
   *
   */
  constructor(private readonly strategy: UnleashStrategy) {
    super(strategy.name);
  }

  isEnabled(parameters: any, context: Context): boolean {
    return this.strategy.isEnabled(
      parameters,
      this.contextToUnleashContext(context),
    );
  }

  contextToUnleashContext(context: Context): UnleashContext {
    let request: any = {};
    const unleashContext = new UnleashContext(
      {
        session: context.sessionId ? { id: context.sessionId } : undefined,
        user: context.userId ? { id: context.userId } : undefined,
        ip: context.remoteAddress,
      },
      { url: '', appName: context.appName ?? '', instanceId: '' }, // Just fill so it works
    );
    unleashContext.extend(context.properties);
    return unleashContext;
  }
}

And you use it like this:

import { ProxifyStrategy } from "./proxy-strategy";
import { RelatedEnterpriseStrategy } from '@precise/nestjs-utils/dist/unleash';

require('dotenv').config()


const { createApp } = require('@unleash/proxy');


const app = createApp({
    unleashUrl: process.env.UNLEASH_URL,
    unleashApiToken: process.env.UNLEASH_API_TOKEN,
    proxySecrets: [process.env.PROXY_SECRET],
    refreshInterval: process.env.REFRESH_INTERVAL,
    customStrategies: [ProxifyStrategy(RelatedEnterpriseStrategy)]
    // logLevel: 'info',
    // projectName: 'order-team',
    // environment: 'development',
});

app.listen(process.env.PORT, () =>
    // eslint-disable-next-line no-console
    console.log(`Unleash Proxy listening on http://localhost:${process.env.PORT}/proxy`),
);

Make it clearer in the doc how to setup feature flag

It's not really clear what to provide when configuring the module,

maybe do this instead

UnleashModule.forRoot({
    url: 'https://<path-to-your-unleash-server>/api/client',
    appName: '<name-of-your-app-on-unleash>',
    instanceId: '<pick-a-unique-id-to-identify-this-nest-server-on-unleash>',
    http: {
      headers: {
        Authorization:
          '<your-token-for-the-environment>',
      },
    },
  }),

Is this project dead?

I have a pending PR for months without any feedback, issues opened, nothing.

If this project is dead or deprecated it should be stated, as the implementation here isn't just wrapper to the original node library, but a full rehaul.

Missing @nestjs/schedule dependency

When trying to build my project using this repo I'm getting below error.
Should I add @nestjs/schedule into my package (which should be noted in readme.md) or it will be added into unleash repo?

nest build

node_modules/nestjs-unleash/dist/src/unleash/updaters/base-updater.d.ts:2:35 - error TS2307: Cannot find module '@nestjs/schedule' or its corresponding type declarations.

2 import { SchedulerRegistry } from '@nestjs/schedule';
                                    ~~~~~~~~~~~~~~~~~~
node_modules/nestjs-unleash/dist/src/unleash/updaters/metrics-updater.service.d.ts:1:35 - error TS2307: Cannot find module '@nestjs/schedule' or its corresponding type declarations.

1 import { SchedulerRegistry } from '@nestjs/schedule';
                                    ~~~~~~~~~~~~~~~~~~
node_modules/nestjs-unleash/dist/src/unleash/updaters/toggles-updater.service.d.ts:1:35 - error TS2307: Cannot find module '@nestjs/schedule' or its corresponding type declarations.

1 import { SchedulerRegistry } from '@nestjs/schedule';
                                    ~~~~~~~~~~~~~~~~~~

Found 3 error(s).

Idea: Deactivate request debug logging

Hello ๐Ÿ‘‹

Thank you for this awesome package!

In my case, I want debugging logs, but I don't really need to get one logging message each time features are updated. Maybe it could be a way to deactivate it, or maybe there is?

Add option to disable debug

The logs are useful for development but spammy in production environments, although we can filter it out in Logger interface, it would be even better if this was an option to turn on/off in the library itself

Module not found

Hi, I'm trying to use your plugin with nestjs and Typescript but all the time I'm getting error that module was not found
import { IfEnabled } from "nestjs-unleash";

Cannot find module 'nestjs-unleash' or its corresponding type declarations.ts(2307)

Yep, I did npm install and it's in package.js.

Allow custom getUserId function

It would be nice to be abe to customize how the user id is computed, as it might not be the one in the user object of a context

Is UnleashStrategiesModule part of the relevant providers/imports within UnleashModule?

Hi, I'm trying to register the UnleashModule in my root app.module with the forRootAsync function and i get the following error:

[Nest] 6291 - 09/12/2021, 12:42:22 PM LOG [NestFactory] Starting Nest application...
[Nest] 6291 - 09/12/2021, 12:42:22 PM ERROR [ExceptionHandler] Nest cannot export a provider/module that is not a part of the currently processed module (UnleashModule). Please verify whether the exported UnleashStrategiesModule is available in this particular context.

Possible Solutions:

  • Is UnleashStrategiesModule part of the relevant providers/imports within UnleashModule?
import { UnleashModule } from 'nestjs-unleash';

@Module({
  imports: [
    ConfigModule.forRoot(),
    UnleashModule.forRootAsync({
      useFactory: (config: ConfigService) => {
        return {
          url: config.get('UNLEASH_URL'),
          appName: config.get('UNLEASH_APP_NAME'),
          instanceId: config.get('UNLEASH_INSTANCE_ID'),
        };
      },
      inject: [ConfigService],
    }),
  ],
  controllers: [
    AppController,
  ],
  providers: [
    AppService,
  ],
})
export class AppModule {
}

package.json:

{
"dependencies": {
    "@nestjs/axios": "0.0.1",
    "@nestjs/common": "^8.0.1",
    "@nestjs/config": "^1.0.0",
    "@nestjs/core": "^8.0.1",
    "@nestjs/event-emitter": "^1.0.0",
    "@nestjs/jwt": "^8.0.0",
    "@nestjs/passport": "^8.0.0",
    "@nestjs/platform-express": "^8.0.1",
    "@nestjs/schedule": "^1.0.1",
    "@prisma/client": "^2.26.0",
    "axios": "^0.21.1",
    "bcrypt": "^5.0.1",
    "cloudevents": "^4.0.3",
    "hash-wasm": "^4.8.0",
    "mssql": "^7.1.3",
    "nestjs-unleash": "^2.0.0",
    "passport": "^0.4.1",
    "passport-jwt": "^4.0.0",
    "passport-local": "^1.0.0",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^6.6.6"
  }
}

[BUG] Conflict with WebSocketGateway

When using UnleashModule in a WebSocketGateway, services are no longer instanciated.

When trying to use a service, it is always undefined.
If I use this.debuggerService.info I'll get an error "Cannot use info of undefined".

If I don't inject UnleashModule (or a service using UnleashModule) I don't have the issue.

Allow to disable registration

I'm using this lib with the gitlab feature flipping, however, I got a stacktrace on startup saying that the Request: POST https://git.corp.dawex.net/api/v4/feature_flags/unleash/326/register failed with 404.

My understanding is that gitlab does not support registering app through the unleash api.

I would like to be able to disable registration so I don"t have those calls made

Metrics not working

There are no metrics reports or application reports.

This is over my head to assist, no idea how this works.

SyntaxError: Unexpected token '?'

Hi, when I'm running it directly in my pc via npm run start it's working but when I't trying to run it in docker I'm getting below error.
I'm not sure where it's coming from. Do you have any idea what might be the reason?

bot-gateway_1  |                 ...(options.extraProviders ?? []),
bot-gateway_1  |                                             ^
bot-gateway_1  | 
bot-gateway_1  | SyntaxError: Unexpected token '?'
bot-gateway_1  |     at Module._compile (internal/modules/cjs/loader.js:895:18)
bot-gateway_1  |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
bot-gateway_1  |     at Module.load (internal/modules/cjs/loader.js:815:32)
bot-gateway_1  |     at Function.Module._load (internal/modules/cjs/loader.js:727:14)
bot-gateway_1  |     at Module.require (internal/modules/cjs/loader.js:852:19)
bot-gateway_1  |     at Module.Hook._require.Module.require (/usr/src/app/node_modules/require-in-the-middle/index.js:80:39)
bot-gateway_1  |     at require (internal/modules/cjs/helpers.js:74:18)
bot-gateway_1  |     at Object.<anonymous> (/usr/src/app/node_modules/nestjs-unleash/src/unleash-strategies/index.ts:3:1)
bot-gateway_1  |     at Module._compile (internal/modules/cjs/loader.js:959:30)
bot-gateway_1  |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)

My dependencies

  "dependencies": {
    "@google-cloud/bigquery": "^5.5.0",
    "@google-cloud/pubsub": "^2.7.0",
    "@google-cloud/trace-agent": "^5.1.1",
    "@nestjs/common": "^7.6.1",
    "@nestjs/config": "^0.4.2",
    "@nestjs/core": "^7.6.1",
    "@nestjs/jwt": "^7.2.0",
    "@nestjs/passport": "^7.1.5",
    "@nestjs/platform-express": "^7.6.1",
    "@nestjs/schedule": "^0.4.1",
    "@nestjs/swagger": "^4.7.6",
    "@nestjs/terminus": "^7.0.1",
    "class-transformer": "^0.2.3",
    "class-validator": "^0.12.2",
    "compression": "^1.7.4",
    "dayjs": "^1.9.7",
    "dotenv": "^8.2.0",
    "gcp-metadata": "^4.2.1",
    "helmet": "^3.23.3",
    "lru-cache": "^6.0.0",
    "measurement-protocol": "^0.1.1",
    "nestjs-pino": "^1.3.0",
    "nestjs-unleash": "^1.2.0",
    "passport": "^0.4.1",
    "passport-http": "^0.3.0",
    "passport-local": "^1.0.0",
    "pino-pretty": "^4.3.0",
    "pino-stackdriver": "2.1.1",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^6.6.3",
    "source-map-support": "0.5.18"
  },
  "devDependencies": {
    "@nestjs/cli": "^7.5.4",
    "@nestjs/schematics": "^7.2.5",
    "@nestjs/testing": "^7.6.1",
    "@types/express": "^4.17.9",
    "@types/helmet": "0.0.47",
    "@types/jest": "25.1.4",
    "@types/lru-cache": "^5.1.0",
    "@types/node": "^13.13.36",
    "@types/passport-http": "^0.3.8",
    "@types/passport-local": "^1.0.33",
    "@types/supertest": "^2.0.10",
    "@types/universal-analytics": "^0.4.4",
    "@typescript-eslint/eslint-plugin": "^2.34.0",
    "@typescript-eslint/parser": "^2.34.0",
    "commander": "^6.2.1",
    "eslint": "^6.8.0",
    "eslint-config-prettier": "^6.15.0",
    "eslint-plugin-import": "^2.22.1",
    "husky": "^4.3.6",
    "jest": "^26.6.3",
    "jest-junit": "10.0.0",
    "prettier": "^1.19.1",
    "pretty-quick": "^2.0.2",
    "supertest": "^4.0.2",
    "ts-jest": "25.5.0",
    "ts-loader": "^6.2.1",
    "ts-node": "^8.10.2",
    "tsconfig-paths": "^3.9.0",
    "typescript": "^4.1.3"
  },

Variant Support

There is no way to use variants using this library.
Any chance you can add it?

Thanks!

P.S if you need assistance let me know, but I'm unsure as to when I'll be able to do it.

The version of @nestjs/axios has reached 2.0.0

The nestjs/axios package has been bumped to version 2.0.0. Our project uses nestjs/axios, which makes it difficult to use this package.

In the peerDependencies of this package, the version of @nestjs/axios is listed as "^1.0.1". I understand that this does not support 2.0.0.

I would like to fork this package, update the peer dependencies, and then apply it to my project, is this a good way to do it? I would be grateful if you could point me to a good way.

Passing context to isEnabled

As part of the official packages, you can always pass in the context object, but in this library, you cannot. The context is only part of the request and is piggybacking on it. Why is it so?
Let's assume I have some context which is calculated as part of the service/controller and doesn't arrive from the client in the Request, using this library it seems I can't use that data as part of the context.

Can this somehow be altered?

Thanks!

P.S
I was sure at the beginning it's just a wrapper around the node SDK, but you actually wrote everything from scratch! That's amazing!

UnleashService doesn't evaluate constrains

I've added a toggle with a standard startegy and added a constraint that a context value should be in a list of values.
the unleash playground evaluated the flag correctly.
seems like the node-sdk supports this

Document already implemented strategy

I had to check the source code to understand why my strategy was not called.

After some digging, I happily found out that there was some strategies already implemented. It would be nice to have a list of the implemented strategies and how they work

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.