Skip to content

Commit

Permalink
Merge pull request #2 from pmb0/feature/register-async
Browse files Browse the repository at this point in the history
feat: add forRootAsync()
  • Loading branch information
pmb0 authored Dec 7, 2020
2 parents 4ef8a88 + 5fdc3c5 commit 478f4c8
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 53 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
# Table of contents <!-- omit in toc -->

- [Usage](#usage)
- [Synchronous configuration](#synchronous-configuration)
- [Asynchronous configuration](#asynchronous-configuration)
- [Usage in controllers or providers](#usage-in-controllers-or-providers)
- [Configuration](#configuration)
- [Custom strategies](#custom-strategies)
- [License](#license)
Expand All @@ -27,7 +30,11 @@
$ npm install --save nestjs-unleash
```

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

## Synchronous configuration

Use `UnleashModule.forRoot()`. Available ptions are described in the [UnleashModuleOptions interface](#configuration).

```ts
@Module({
Expand All @@ -42,6 +49,30 @@ Import the module with `UnleashModule.forRoot(...)`:
export class MyModule {}
```

## Asynchronous configuration

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

```ts
@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:

```ts
Expand Down
29 changes: 20 additions & 9 deletions e2e/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,28 @@ import { UsersService } from './users.service'

@Module({
imports: [
UnleashModule.forRoot({
// url: 'http://127.0.0.1:3000/unleash',
url: 'https://unleash.herokuapp.com/api/client',
appName: 'my-app-name',
instanceId: process.pid.toString(),
refreshInterval: 20000,
// metricsInterval: 3000,
strategies: [MyCustomStrategy],
// UnleashModule.forRoot({
// // url: 'http://127.0.0.1:3000/unleash',
// url: 'https://unleash.herokuapp.com/api/client',
// appName: 'my-app-name',
// instanceId: 'my-unique-instance', //process.pid.toString(),
// refreshInterval: 20000,
// // metricsInterval: 3000,
// strategies: [MyCustomStrategy],
// }),
UnleashModule.forRootAsync({
useFactory: () => ({
// url: 'http://127.0.0.1:3000/unleash',
url: 'https://unleash.herokuapp.com/api/client',
appName: 'my-app-name',
instanceId: 'my-unique-instance', //process.pid.toString(),
refreshInterval: 20000,
// metricsInterval: 3000,
// strategies: [MyCustomStrategy],
}),
}),
],
providers: [UsersService],
providers: [UsersService, MyCustomStrategy],
controllers: [AppController, UnleashMockController],
})
export class ApplicationModule {}
18 changes: 18 additions & 0 deletions src/unleash-client/unleash-client.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import { ModuleMetadata, Type } from '@nestjs/common'

export interface UnleashClientModuleOptions {
baseURL: string
instanceId: string
appName: string
timeout: number
}

export interface UnleashClientModuleOptionsFactory {
createStrategiesOptions():
| Promise<UnleashClientModuleOptions>
| UnleashClientModuleOptions
}

export interface UnleashClientModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
inject?: any[]
useExisting?: Type<UnleashClientModuleOptionsFactory>
useClass?: Type<UnleashClientModuleOptionsFactory>
useFactory?: (
...args: any[]
) => Promise<UnleashClientModuleOptions> | UnleashClientModuleOptions
}
40 changes: 37 additions & 3 deletions src/unleash-client/unleash-client.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DynamicModule, HttpModule, Module } from '@nestjs/common'
import { DynamicModule, HttpModule, Module, Provider } from '@nestjs/common'
import {
UnleashFeaturesClient,
UnleashMetricsClient,
Expand All @@ -7,7 +7,10 @@ import {
import { UnleashStrategiesModule } from '..'
import { UnleashClient } from './unleash-client'
import { UNLEASH_CLIENT_OPTIONS } from './unleash-client.constants'
import { UnleashClientModuleOptions } from './unleash-client.interfaces'
import {
UnleashClientModuleAsyncOptions,
UnleashClientModuleOptions,
} from './unleash-client.interfaces'

@Module({})
export class UnleashClientModule {
Expand All @@ -25,14 +28,45 @@ export class UnleashClientModule {
}),
UnleashStrategiesModule,
],
providers: [{ provide: UNLEASH_CLIENT_OPTIONS, useValue: options }],
}
}

public static registerAsync(
options: UnleashClientModuleAsyncOptions,
): DynamicModule {
const provider: Provider = {
provide: UNLEASH_CLIENT_OPTIONS,
useFactory: options.useFactory!,
inject: options.inject,
}
return {
module: UnleashStrategiesModule,
imports: [
...(options.imports ?? []),
HttpModule.registerAsync({
useFactory: (options: UnleashClientModuleOptions) => ({
baseURL: options.baseURL,
headers: {
'UNLEASH-INSTANCEID': options.instanceId,
'UNLEASH-APPNAME': options.appName,
},
timeout: options.timeout,
}),
inject: [UNLEASH_CLIENT_OPTIONS],
}),
],
providers: [
{ provide: UNLEASH_CLIENT_OPTIONS, useValue: options },
provider,
UnleashStrategiesModule,
UnleashClient,
UnleashFeaturesClient,
UnleashMetricsClient,
UnleashRegisterClient,
],
exports: [
provider,
UnleashClient,
UnleashFeaturesClient,
UnleashMetricsClient,
UnleashRegisterClient,
Expand Down
24 changes: 24 additions & 0 deletions src/unleash-strategies/unleash-strategies.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { ModuleMetadata, Provider, Type } from '@nestjs/common'
import { UnleashStrategy } from './strategy'

// https://github.com/SerayaEryn/fastify-session/blob/master/types/types.d.ts
export interface FastifySession {
sessionId: string
Expand Down Expand Up @@ -27,3 +30,24 @@ export interface Context {
appName?: string
properties?: Properties
}

export interface UnleashStrategiesOptionsFactory {
createStrategiesOptions():
| Promise<UnleashStrategiesModuleOptions>
| UnleashStrategiesModuleOptions
}

export interface UnleashStrategiesModuleOptions {
strategies: Type<UnleashStrategy>[]
}

export interface UnleashStrategiesModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
extraProviders?: Provider[]
inject?: any[]
useExisting?: Type<UnleashStrategiesOptionsFactory>
useClass?: Type<UnleashStrategiesOptionsFactory>
useFactory?: (
...args: any[]
) => Promise<UnleashStrategiesModuleOptions> | UnleashStrategiesModuleOptions
}
90 changes: 76 additions & 14 deletions src/unleash-strategies/unleash-strategies.module.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,99 @@
import { DynamicModule, Module, Type } from '@nestjs/common'
import { DynamicModule, Module, Provider } from '@nestjs/common'
import {
UnleashStrategiesModuleAsyncOptions,
UnleashStrategiesModuleOptions,
UnleashStrategiesOptionsFactory,
} from '.'
import {
ApplicationHostnameStrategy,
DefaultStrategy,
FlexibleRolloutStrategy,
GradualRolloutSessionIdStrategy,
RemoteAddressStrategy,
UnleashStrategy,
UserWithIdStrategy,
} from './strategy'
import { GradualRolloutRandomStrategy } from './strategy/gradual-rollout-random'
import { GradualRolloutUserIdStrategy } from './strategy/gradual-rollout-user-id'
import { CUSTOM_STRATEGIES } from './unleash-strategies.constants'
import { UnleashStrategiesService } from './unleash-strategies.service'

@Module({})
@Module({
providers: [
DefaultStrategy,
ApplicationHostnameStrategy,
FlexibleRolloutStrategy,
RemoteAddressStrategy,
UserWithIdStrategy,
GradualRolloutRandomStrategy,
GradualRolloutSessionIdStrategy,
GradualRolloutUserIdStrategy,
],
})
export class UnleashStrategiesModule {
static register(strategies: Type<UnleashStrategy>[] = []): DynamicModule {
static register({
strategies = [],
}: UnleashStrategiesModuleOptions): DynamicModule {
return {
module: UnleashStrategiesModule,
providers: [
UnleashStrategiesService,
DefaultStrategy,
ApplicationHostnameStrategy,
FlexibleRolloutStrategy,
RemoteAddressStrategy,
UserWithIdStrategy,
GradualRolloutRandomStrategy,
GradualRolloutSessionIdStrategy,
GradualRolloutUserIdStrategy,
...strategies,
{ provide: CUSTOM_STRATEGIES, useValue: strategies },
],
exports: [UnleashStrategiesService],
exports: [
UnleashStrategiesService,
{ provide: CUSTOM_STRATEGIES, useValue: strategies },
],
}
}

public static registerAsync(
options: UnleashStrategiesModuleAsyncOptions,
): DynamicModule {
const providers = this.createStrategiesProviders(options)
return {
module: UnleashStrategiesModule,
imports: options.imports,
providers: [
...(options.extraProviders ?? []),
...providers,
UnleashStrategiesService,
],
exports: [...providers, UnleashStrategiesService],
}
}

static createStrategiesProviders(
options: UnleashStrategiesModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createStrategiesOptionsProvider(options)]
}

return [
this.createStrategiesOptionsProvider(options),
{
provide: options.useClass!,
useClass: options.useClass!,
},
]
}

private static createStrategiesOptionsProvider(
options: UnleashStrategiesModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: CUSTOM_STRATEGIES,
useFactory: options.useFactory,
inject: options.inject,
}
}

return {
provide: CUSTOM_STRATEGIES,
useFactory: async (optionsFactory: UnleashStrategiesOptionsFactory) =>
await optionsFactory.createStrategiesOptions(),
inject: [options.useExisting || options.useClass!],
}
}
}
18 changes: 13 additions & 5 deletions src/unleash-strategies/unleash-strategies.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Inject, Injectable, Type } from '@nestjs/common'
import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
import { ModuleRef } from '@nestjs/core'
import { UnleashStrategiesModuleOptions } from '.'
import {
ApplicationHostnameStrategy,
DefaultStrategy,
Expand All @@ -14,7 +15,7 @@ import { GradualRolloutUserIdStrategy } from './strategy/gradual-rollout-user-id
import { CUSTOM_STRATEGIES } from './unleash-strategies.constants'

@Injectable()
export class UnleashStrategiesService {
export class UnleashStrategiesService implements OnModuleInit {
private strategies: UnleashStrategy[]

constructor(
Expand All @@ -26,8 +27,9 @@ export class UnleashStrategiesService {
private readonly gradualRolloutRandom: GradualRolloutRandomStrategy,
private readonly gradualRolloutUserId: GradualRolloutUserIdStrategy,
private readonly gradualRolloutSessionId: GradualRolloutSessionIdStrategy,
@Inject(CUSTOM_STRATEGIES) strategies: Type<UnleashStrategy>[] = [],
module: ModuleRef,
@Inject(CUSTOM_STRATEGIES)
private readonly options: UnleashStrategiesModuleOptions,
private readonly moduleRef: ModuleRef,
) {
this.strategies = [
userWithId,
Expand All @@ -38,10 +40,16 @@ export class UnleashStrategiesService {
gradualRolloutRandom,
gradualRolloutUserId,
gradualRolloutSessionId,
...strategies.map((strategy) => module.get(strategy)),
// ...strategies.map((strategy) => module.get(strategy)),
]
}

async onModuleInit(): Promise<void> {
for (const customStrategy of this.options.strategies) {
this.strategies.push(await this.moduleRef.create(customStrategy))
}
}

findAll(): UnleashStrategy[] {
return this.strategies
}
Expand Down
1 change: 1 addition & 0 deletions src/unleash/unleash.constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const REFRESH_INTERVAL = 'REFRESH_INTERVAL'
export const METRICS_INTERVAL = 'METRICS_INTERVAL'
export const APP_NAME = 'APP_NAME'
export const UNLEASH_MODULE_OPTIONS = 'UNLEASH_MODULE_OPTIONS'
12 changes: 11 additions & 1 deletion src/unleash/unleash.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Type } from '@nestjs/common'
import { ModuleMetadata, Provider, Type } from '@nestjs/common'
import { UnleashStrategy } from '../unleash-strategies'

export interface UnleashModuleOptions {
Expand Down Expand Up @@ -45,3 +45,13 @@ export interface UnleashModuleOptions {
*/
strategies?: Type<UnleashStrategy>[]
}

export interface UnleashModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
global?: boolean
extraProviders?: Provider[]
inject?: any[]
useFactory?: (
...args: any[]
) => Promise<UnleashModuleOptions> | UnleashModuleOptions
}
Loading

0 comments on commit 478f4c8

Please sign in to comment.