Skip to content

Commit 840d7ac

Browse files
feat(nestjs)!: add domains (#831)
<!-- Please use this template for your pull request. --> <!-- Please use the sections that you need and delete other sections --> ## This PR <!-- add the description of the PR here --> - adds support for domains - removes named clients ### Related Issues <!-- add here the GitHub issue that this PR resolves if applicable --> relates to open-feature/spec#229 --------- Signed-off-by: Lukas Reining <[email protected]> Co-authored-by: Michael Beemer <[email protected]>
1 parent 6034df4 commit 840d7ac

File tree

7 files changed

+44
-51
lines changed

7 files changed

+44
-51
lines changed

packages/nest/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class OpenFeatureController {
126126
}
127127
```
128128

129-
It is also possible to inject the default or named OpenFeature clients into a service via Nest dependency injection system.
129+
It is also possible to inject the default or domain scoped OpenFeature clients into a service via Nest dependency injection system.
130130

131131
```ts
132132
import { Injectable } from '@nestjs/common';
@@ -136,7 +136,7 @@ import { FeatureClient, Client } from '@openfeature/nestjs-sdk';
136136
export class OpenFeatureTestService {
137137
constructor(
138138
@FeatureClient() private defaultClient: Client,
139-
@FeatureClient({ name: 'differentProvider' }) private namedClient: Client,
139+
@FeatureClient({ domain: 'my-domain' }) private scopedClient: Client,
140140
) {}
141141

142142
public async getBoolean() {

packages/nest/src/feature.decorator.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,28 @@ import { from, Observable } from 'rxjs';
1515
*/
1616
interface FeatureClientProps {
1717
/**
18-
* The name of the OpenFeature client, if a named client should be used.
18+
* The domain of the OpenFeature client, if a domain scoped client should be used.
1919
* @see {@link Client.getBooleanDetails}
2020
*/
21-
name?: string;
21+
domain?: string;
2222
}
2323

2424
/**
2525
* Injects a feature client into a constructor or property of a class.
2626
* @param {FeatureClientProps} [props] The options for injecting the client.
2727
* @returns {PropertyDecorator & ParameterDecorator} The decorator function.
2828
*/
29-
export const FeatureClient = (props?: FeatureClientProps) => Inject(getOpenFeatureClientToken(props?.name));
29+
export const FeatureClient = (props?: FeatureClientProps) => Inject(getOpenFeatureClientToken(props?.domain));
3030

3131
/**
3232
* Options for injecting a feature flag into a route handler.
3333
*/
3434
interface FeatureProps<T extends FlagValue> {
3535
/**
36-
* The name of the OpenFeature client, if a named client should be used.
36+
* The domain of the OpenFeature client, if a domain scoped client should be used.
3737
* @see {@link OpenFeature#getClient}
3838
*/
39-
clientName?: string;
39+
domain?: string;
4040
/**
4141
* The key of the feature flag.
4242
* @see {@link Client#getBooleanDetails}
@@ -55,19 +55,19 @@ interface FeatureProps<T extends FlagValue> {
5555
}
5656

5757
/**
58-
* Returns a named or unnamed OpenFeature client with the given context.
59-
* @param {string} clientName The name of the OpenFeature client.
58+
* Returns a domain scoped or the default OpenFeature client with the given context.
59+
* @param {string} clientDomain The domain of the OpenFeature client.
6060
* @param {EvaluationContext} context The evaluation context of the client.
6161
* @returns {Client} The OpenFeature client.
6262
*/
63-
function getClientForEvaluation(clientName?: string, context?: EvaluationContext) {
64-
return clientName ? OpenFeature.getClient(clientName, context) : OpenFeature.getClient(context);
63+
function getClientForEvaluation(domain?: string, context?: EvaluationContext) {
64+
return domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context);
6565
}
6666

6767
/**
6868
* Route handler parameter decorator.
6969
*
70-
* Gets the {@link EvaluationDetails} for given feature flag from a named or unnamed OpenFeature
70+
* Gets the {@link EvaluationDetails} for given feature flag from a domain scoped or the default OpenFeature
7171
* client and populates the annotated parameter with the {@link EvaluationDetails} wrapped in an {@link Observable}.
7272
*
7373
* For example:
@@ -82,16 +82,16 @@ function getClientForEvaluation(clientName?: string, context?: EvaluationContext
8282
* @returns {ParameterDecorator}
8383
*/
8484
export const BooleanFeatureFlag = createParamDecorator(
85-
({ clientName, flagKey, defaultValue, context }: FeatureProps<boolean>): Observable<EvaluationDetails<boolean>> => {
86-
const client = getClientForEvaluation(clientName, context);
85+
({ domain, flagKey, defaultValue, context }: FeatureProps<boolean>): Observable<EvaluationDetails<boolean>> => {
86+
const client = getClientForEvaluation(domain, context);
8787
return from(client.getBooleanDetails(flagKey, defaultValue));
8888
},
8989
);
9090

9191
/**
9292
* Route handler parameter decorator.
9393
*
94-
* Gets the {@link EvaluationDetails} for given feature flag from a named or unnamed OpenFeature
94+
* Gets the {@link EvaluationDetails} for given feature flag from a domain scoped or the default OpenFeature
9595
* client and populates the annotated parameter with the {@link EvaluationDetails} wrapped in an {@link Observable}.
9696
*
9797
* For example:
@@ -106,16 +106,16 @@ export const BooleanFeatureFlag = createParamDecorator(
106106
* @returns {ParameterDecorator}
107107
*/
108108
export const StringFeatureFlag = createParamDecorator(
109-
({ clientName, flagKey, defaultValue, context }: FeatureProps<string>): Observable<EvaluationDetails<string>> => {
110-
const client = getClientForEvaluation(clientName, context);
109+
({ domain, flagKey, defaultValue, context }: FeatureProps<string>): Observable<EvaluationDetails<string>> => {
110+
const client = getClientForEvaluation(domain, context);
111111
return from(client.getStringDetails(flagKey, defaultValue));
112112
},
113113
);
114114

115115
/**
116116
* Route handler parameter decorator.
117117
*
118-
* Gets the {@link EvaluationDetails} for given feature flag from a named or unnamed OpenFeature
118+
* Gets the {@link EvaluationDetails} for given feature flag from a domain scoped or the default OpenFeature
119119
* client and populates the annotated parameter with the {@link EvaluationDetails} wrapped in an {@link Observable}.
120120
*
121121
* For example:
@@ -130,16 +130,16 @@ export const StringFeatureFlag = createParamDecorator(
130130
* @returns {ParameterDecorator}
131131
*/
132132
export const NumberFeatureFlag = createParamDecorator(
133-
({ clientName, flagKey, defaultValue, context }: FeatureProps<number>): Observable<EvaluationDetails<number>> => {
134-
const client = getClientForEvaluation(clientName, context);
133+
({ domain, flagKey, defaultValue, context }: FeatureProps<number>): Observable<EvaluationDetails<number>> => {
134+
const client = getClientForEvaluation(domain, context);
135135
return from(client.getNumberDetails(flagKey, defaultValue));
136136
},
137137
);
138138

139139
/**
140140
* Route handler parameter decorator.
141141
*
142-
* Gets the {@link EvaluationDetails} for given feature flag from a named or unnamed OpenFeature
142+
* Gets the {@link EvaluationDetails} for given feature flag from a domain scoped or the default OpenFeature
143143
* client and populates the annotated parameter with the {@link EvaluationDetails} wrapped in an {@link Observable}.
144144
*
145145
* For example:
@@ -154,13 +154,8 @@ export const NumberFeatureFlag = createParamDecorator(
154154
* @returns {ParameterDecorator}
155155
*/
156156
export const ObjectFeatureFlag = createParamDecorator(
157-
({
158-
clientName,
159-
flagKey,
160-
defaultValue,
161-
context,
162-
}: FeatureProps<JsonValue>): Observable<EvaluationDetails<JsonValue>> => {
163-
const client = getClientForEvaluation(clientName, context);
157+
({ domain, flagKey, defaultValue, context }: FeatureProps<JsonValue>): Observable<EvaluationDetails<JsonValue>> => {
158+
const client = getClientForEvaluation(domain, context);
164159
return from(client.getObjectDetails(flagKey, defaultValue));
165160
},
166161
);

packages/nest/src/open-feature.module.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ export class OpenFeatureModule {
5555
}
5656

5757
if (options?.providers) {
58-
Object.entries(options.providers).forEach(([name, provider]) => {
59-
OpenFeature.setProvider(name, provider);
58+
Object.entries(options.providers).forEach(([domain, provider]) => {
59+
OpenFeature.setProvider(domain, provider);
6060
clientValueProviders.push({
61-
provide: getOpenFeatureClientToken(name),
62-
useFactory: () => OpenFeature.getClient(name),
61+
provide: getOpenFeatureClientToken(domain),
62+
useFactory: () => OpenFeature.getClient(domain),
6363
});
6464
});
6565
}
@@ -100,11 +100,11 @@ export interface OpenFeatureModuleOptions {
100100
*/
101101
defaultProvider?: Provider;
102102
/**
103-
* Named providers to set to OpenFeature.
103+
* Domain scoped providers to set to OpenFeature.
104104
* @see {@link OpenFeature#setProvider}
105105
*/
106106
providers?: {
107-
[providerName: string]: Provider;
107+
[domain: string]: Provider;
108108
};
109109
/**
110110
* Global {@link Logger} for OpenFeature.
@@ -149,10 +149,10 @@ export interface OpenFeatureModuleOptions {
149149
}
150150

151151
/**
152-
* Returns an injection token for a (named) OpenFeature client.
153-
* @param {string} name The name of the OpenFeature client.
152+
* Returns an injection token for a (domain scoped) OpenFeature client.
153+
* @param {string} domain The domain of the OpenFeature client.
154154
* @returns {Client} The injection token.
155155
*/
156-
export function getOpenFeatureClientToken(name?: string): string {
157-
return name ? `OpenFeatureClient_${name}` : 'OpenFeatureClient_default';
156+
export function getOpenFeatureClientToken(domain?: string): string {
157+
return domain ? `OpenFeatureClient_${domain}` : 'OpenFeatureClient_default';
158158
}

packages/nest/test/fixtures.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,16 @@ export const defaultProvider = new InMemoryProvider({
2525
},
2626
});
2727

28-
2928
export const providers = {
30-
namedClient: new InMemoryProvider({
29+
domainScopedClient: new InMemoryProvider({
3130
testBooleanFlag: {
3231
defaultVariant: 'default',
3332
variants: { default: true },
3433
disabled: false,
3534
},
3635
testStringFlag: {
3736
defaultVariant: 'default',
38-
variants: { default: 'expected-string-value-named' },
37+
variants: { default: 'expected-string-value-scoped' },
3938
disabled: false,
4039
},
4140
testNumberFlag: {
@@ -45,13 +44,12 @@ export const providers = {
4544
},
4645
testObjectFlag: {
4746
defaultVariant: 'default',
48-
variants: { default: { client: 'named' } },
47+
variants: { default: { client: 'scoped' } },
4948
disabled: false,
5049
},
5150
}),
5251
};
5352

54-
5553
export const exampleContextFactory = async (context: ExecutionContext) => {
5654
const request = await context.switchToHttp().getRequest();
5755

@@ -70,6 +68,6 @@ export const getOpenFeatureDefaultTestModule = () => {
7068
return OpenFeatureModule.forRoot({
7169
contextFactory: exampleContextFactory,
7270
defaultProvider,
73-
providers
71+
providers,
7472
});
7573
};

packages/nest/test/open-feature-sdk.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ describe('OpenFeature SDK', () => {
3535
'expected-string-value-default',
3636
);
3737

38-
expect(testService.namedClient).toBeDefined();
39-
expect(await testService.namedClient.getStringValue('testStringFlag', 'wrong-value')).toEqual(
40-
'expected-string-value-named',
38+
expect(testService.domainScopedClient).toBeDefined();
39+
expect(await testService.domainScopedClient.getStringValue('testStringFlag', 'wrong-value')).toEqual(
40+
'expected-string-value-scoped',
4141
);
4242
});
4343
});

packages/nest/test/open-feature.module.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ describe('OpenFeatureModule', () => {
4242
expect(await client.getStringValue('testStringFlag', '')).toEqual('expected-string-value-default');
4343
});
4444

45-
it('should inject the client with the given name', async () => {
46-
const client = moduleRef.get<OpenFeatureClient>(getOpenFeatureClientToken('namedClient'));
45+
it('should inject the client with the given scope', async () => {
46+
const client = moduleRef.get<OpenFeatureClient>(getOpenFeatureClientToken('domainScopedClient'));
4747
expect(client).toBeDefined();
48-
expect(await client.getStringValue('testStringFlag', '')).toEqual('expected-string-value-named');
48+
expect(await client.getStringValue('testStringFlag', '')).toEqual('expected-string-value-scoped');
4949
});
5050
});
5151

packages/nest/test/test-app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { EvaluationContextInterceptor } from '../src';
88
export class OpenFeatureTestService {
99
constructor(
1010
@FeatureClient() public defaultClient: OpenFeatureClient,
11-
@FeatureClient({ name: 'namedClient' }) public namedClient: OpenFeatureClient,
11+
@FeatureClient({ domain: 'domainScopedClient' }) public domainScopedClient: OpenFeatureClient,
1212
) {}
1313

1414
public async serviceMethod(flag: EvaluationDetails<FlagValue>) {

0 commit comments

Comments
 (0)