Skip to content

Commit c32450a

Browse files
refactor: keep framework metadata internal
Remove the public client metadata options overloads and restore the original getClient signatures. Framework SDKs now mark SDK-owned client instances locally so framework metadata still flows into client and hook metadata without a public API change. Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
1 parent d8d88c8 commit c32450a

12 files changed

Lines changed: 72 additions & 138 deletions

File tree

packages/angular/projects/angular-sdk/src/lib/feature-flag.directive.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export abstract class FeatureFlagDirective<T extends FlagValue> implements OnIni
132132
if (this._client) {
133133
this.disposeClient(this._client);
134134
}
135-
this._client = OpenFeature.getClient(this._featureFlagDomain, { framework: 'angular' });
135+
this._client = setAngularFrameworkMetadata(OpenFeature.getClient(this._featureFlagDomain));
136136

137137
const baseHandler = () => {
138138
const result = this.getFlagDetails(this._featureFlagKey, this._featureFlagDefault);
@@ -230,6 +230,15 @@ export abstract class FeatureFlagDirective<T extends FlagValue> implements OnIni
230230
}
231231
}
232232

233+
type FrameworkMetadataClient = Client & {
234+
setFrameworkMetadata?: (framework: 'angular') => Client;
235+
};
236+
237+
function setAngularFrameworkMetadata(client: Client): Client {
238+
(client as FrameworkMetadataClient).setFrameworkMetadata?.('angular');
239+
return client;
240+
}
241+
233242
/**
234243
* A structural directive that conditionally includes a template based on the evaluation
235244
* of a boolean feature flag.

packages/angular/projects/angular-sdk/src/lib/feature-flag.service.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export class FeatureFlagService {
217217
domain: string | undefined,
218218
options?: AngularFlagEvaluationOptions,
219219
): Observable<EvaluationDetails<T>> {
220-
const client = OpenFeature.getClient(domain, { framework: 'angular' });
220+
const client = setAngularFrameworkMetadata(OpenFeature.getClient(domain));
221221

222222
return new Observable<EvaluationDetails<T>>((subscriber) => {
223223
let currentResult: EvaluationDetails<T> | undefined = undefined;
@@ -266,3 +266,12 @@ export class FeatureFlagService {
266266
});
267267
}
268268
}
269+
270+
type FrameworkMetadataClient = Client & {
271+
setFrameworkMetadata?: (framework: 'angular') => Client;
272+
};
273+
274+
function setAngularFrameworkMetadata(client: Client): Client {
275+
(client as FrameworkMetadataClient).setFrameworkMetadata?.('angular');
276+
return client;
277+
}

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import { APP_INTERCEPTOR } from '@nestjs/core';
2222
import { EvaluationContextInterceptor } from './evaluation-context-interceptor';
2323
import { ShutdownService } from './shutdown.service';
2424

25+
type FrameworkMetadataClient = Client & {
26+
setFrameworkMetadata?: (framework: 'nest') => Client;
27+
};
28+
2529
/**
2630
* OpenFeatureModule is a NestJS wrapper for OpenFeature Server-SDK.
2731
*/
@@ -45,7 +49,7 @@ export class OpenFeatureModule {
4549
const clientValueProviders: NestFactoryProvider<Client>[] = [
4650
{
4751
provide: getOpenFeatureClientToken(),
48-
useFactory: () => OpenFeature.getClient(undefined, { framework: 'nest' }),
52+
useFactory: () => setNestFrameworkMetadata(OpenFeature.getClient()),
4953
},
5054
];
5155

@@ -58,7 +62,7 @@ export class OpenFeatureModule {
5862
OpenFeature.setProvider(domain, provider);
5963
clientValueProviders.push({
6064
provide: getOpenFeatureClientToken(domain),
61-
useFactory: () => OpenFeature.getClient(domain, { framework: 'nest' }),
65+
useFactory: () => setNestFrameworkMetadata(OpenFeature.getClient(domain)),
6266
});
6367
});
6468
}
@@ -155,3 +159,8 @@ export interface OpenFeatureModuleOptions {
155159
export function getOpenFeatureClientToken(domain?: string): string {
156160
return domain ? `OpenFeatureClient_${domain}` : 'OpenFeatureClient_default';
157161
}
162+
163+
function setNestFrameworkMetadata(client: Client): Client {
164+
(client as FrameworkMetadataClient).setFrameworkMetadata?.('nest');
165+
return client;
166+
}

packages/nest/src/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import type { Client, EvaluationContext } from '@openfeature/server-sdk';
22
import { OpenFeature } from '@openfeature/server-sdk';
33

4+
type FrameworkMetadataClient = Client & {
5+
setFrameworkMetadata?: (framework: 'nest') => Client;
6+
};
7+
48
/**
59
* Returns a domain scoped or the default OpenFeature client with the given context.
610
* @param {string} domain The domain of the OpenFeature client.
711
* @param {EvaluationContext} context The evaluation context of the client.
812
* @returns {Client} The OpenFeature client.
913
*/
1014
export function getClientForEvaluation(domain?: string, context?: EvaluationContext) {
11-
return OpenFeature.getClient(domain, { framework: 'nest' }, context);
15+
return setNestFrameworkMetadata(domain ? OpenFeature.getClient(domain, context) : OpenFeature.getClient(context));
16+
}
17+
18+
function setNestFrameworkMetadata(client: Client): Client {
19+
(client as FrameworkMetadataClient).setFrameworkMetadata?.('nest');
20+
return client;
1221
}

packages/react/src/provider/provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function OpenFeatureProvider({ client, domain, children, ...options }: Pr
4141
return setReactFrameworkMetadata(client);
4242
}
4343

44-
return OpenFeature.getClient(domain, { framework: 'react' });
44+
return setReactFrameworkMetadata(OpenFeature.getClient(domain));
4545
}, [client, domain]);
4646

4747
return <Context.Provider value={{ client: stableClient, options }}>{children}</Context.Provider>;

packages/server/src/client/internal/open-feature-client.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ type OpenFeatureClientOptions = {
4040
name?: string;
4141
domain?: string;
4242
version?: string;
43-
framework?: ClientFramework;
4443
};
4544

4645
/**
@@ -68,7 +67,6 @@ export class OpenFeatureClient implements Client {
6867
context: EvaluationContext = {},
6968
) {
7069
this._context = context;
71-
this._framework = options.framework;
7270
}
7371

7472
get metadata(): ClientMetadata {
@@ -84,6 +82,20 @@ export class OpenFeatureClient implements Client {
8482
};
8583
}
8684

85+
/**
86+
* Sets framework metadata on an existing SDK-owned client instance.
87+
*
88+
* This is used by framework wrappers that must preserve a pre-created client
89+
* instance instead of constructing a new framework-aware client.
90+
* @param {ClientFramework} framework framework metadata to expose
91+
* @returns {this} the updated client
92+
* @internal
93+
*/
94+
setFrameworkMetadata(framework: ClientFramework): this {
95+
this._framework = framework;
96+
return this;
97+
}
98+
8799
get providerStatus(): ProviderStatus {
88100
return this.providerStatusAccessor();
89101
}

packages/server/src/open-feature.ts

Lines changed: 10 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import type {
2-
ClientFramework,
3-
ClientMetadataOptions,
4-
EvaluationContext,
5-
ManageContext,
6-
ServerProviderStatus,
7-
} from '@openfeature/core';
1+
import type { EvaluationContext, ManageContext, ServerProviderStatus } from '@openfeature/core';
82
import { OpenFeatureCommonAPI, ProviderWrapper, objectOrUndefined, stringOrUndefined } from '@openfeature/core';
93
import type { Client } from './client';
104
import { OpenFeatureClient } from './client/internal/open-feature-client';
@@ -187,19 +181,6 @@ export class OpenFeatureAPI
187181
* @returns {Client} OpenFeature Client
188182
*/
189183
getClient(domain: string, context?: EvaluationContext): Client;
190-
/**
191-
* A factory function for creating new domain scoped OpenFeature clients.
192-
* Clients can contain their own state (e.g. logger, hook, context).
193-
* Multiple clients can be used to segment feature flag configuration.
194-
*
195-
* If there is already a provider bound to this domain via {@link this.setProvider setProvider}, this provider will be used.
196-
* Otherwise, the default provider is used until a provider is assigned to that domain.
197-
* @param {string} domain An identifier which logically binds clients with providers
198-
* @param {ClientMetadataOptions} options Client metadata options
199-
* @param {EvaluationContext} context Evaluation context that should be set on the client to used during flag evaluations
200-
* @returns {Client} OpenFeature Client
201-
*/
202-
getClient(domain: string | undefined, options: ClientMetadataOptions, context?: EvaluationContext): Client;
203184
/**
204185
* A factory function for creating new domain scoped OpenFeature clients.
205186
* Clients can contain their own state (e.g. logger, hook, context).
@@ -215,28 +196,20 @@ export class OpenFeatureAPI
215196
getClient(domain: string, version: string, context?: EvaluationContext): Client;
216197
getClient(
217198
domainOrContext?: string | EvaluationContext,
218-
versionOrOptionsOrContext?: string | ClientMetadataOptions | EvaluationContext,
199+
versionOrContext?: string | EvaluationContext,
219200
contextOrUndefined?: EvaluationContext,
220201
): Client {
221202
const domain = stringOrUndefined(domainOrContext);
222-
const options = clientMetadataOptionsOrUndefined(versionOrOptionsOrContext);
223-
const version = stringOrUndefined(versionOrOptionsOrContext) ?? options?.version;
224-
const context = domain
225-
? (objectOrUndefined<EvaluationContext>(options ? contextOrUndefined : versionOrOptionsOrContext) ??
226-
objectOrUndefined<EvaluationContext>(contextOrUndefined))
227-
: objectOrUndefined<EvaluationContext>(
228-
domainOrContext ?? (options ? contextOrUndefined : versionOrOptionsOrContext),
229-
);
203+
const version = stringOrUndefined(versionOrContext);
204+
const context =
205+
objectOrUndefined<EvaluationContext>(domainOrContext) ??
206+
objectOrUndefined<EvaluationContext>(versionOrContext) ??
207+
objectOrUndefined<EvaluationContext>(contextOrUndefined);
230208

231-
return this._createClient(domain, version, context, options?.framework);
209+
return this._createClient(domain, version, context);
232210
}
233211

234-
private _createClient(
235-
domain?: string,
236-
version?: string,
237-
context: EvaluationContext = {},
238-
framework?: ClientFramework,
239-
): Client {
212+
private _createClient(domain?: string, version?: string, context: EvaluationContext = {}): Client {
240213
return new OpenFeatureClient(
241214
() => this.getProviderForClient(domain),
242215
() => this.getProviderStatus(domain),
@@ -245,7 +218,7 @@ export class OpenFeatureAPI
245218
() => this.getHooks(),
246219
() => this.getTransactionContext(),
247220
() => this._logger,
248-
{ domain, version, framework },
221+
{ domain, version },
249222
context,
250223
);
251224
}
@@ -297,13 +270,3 @@ export class OpenFeatureAPI
297270
* @returns {OpenFeatureAPI} OpenFeature API
298271
*/
299272
export const OpenFeature = OpenFeatureAPI.getInstance();
300-
301-
function clientMetadataOptionsOrUndefined(
302-
value: string | ClientMetadataOptions | EvaluationContext | undefined,
303-
): ClientMetadataOptions | undefined {
304-
if (typeof value === 'object' && value && ('version' in value || 'framework' in value)) {
305-
return value as ClientMetadataOptions;
306-
}
307-
308-
return undefined;
309-
}

packages/server/test/open-feature.spec.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -195,23 +195,12 @@ describe('OpenFeature', () => {
195195
const contextOnly = { targetingKey: 'context-only' };
196196
const domainContext = { targetingKey: 'domain-context' };
197197
const versionContext = { targetingKey: 'version-context' };
198-
const metadataContext = { targetingKey: 'metadata-context' };
199198

200199
const defaultClient = OpenFeature.getClient();
201200
const contextClient = OpenFeature.getClient(contextOnly);
202201
const domainClient = OpenFeature.getClient('domain-only');
203202
const domainContextClient = OpenFeature.getClient('domain-context', domainContext);
204203
const legacyVersionClient = OpenFeature.getClient('legacy-version', '1.2.3', versionContext);
205-
const defaultMetadataClient = OpenFeature.getClient(
206-
undefined,
207-
{ version: '2.0.0', framework: 'nest' },
208-
metadataContext,
209-
);
210-
const domainMetadataClient = OpenFeature.getClient(
211-
'options-domain',
212-
{ version: '3.0.0', framework: 'nest' },
213-
metadataContext,
214-
);
215204

216205
expect(defaultClient.metadata).toMatchObject({
217206
sdk: 'js-server',
@@ -242,21 +231,6 @@ describe('OpenFeature', () => {
242231
paradigm: 'server',
243232
});
244233
expect(legacyVersionClient.getContext()).toEqual(versionContext);
245-
expect(defaultMetadataClient.metadata).toMatchObject({
246-
version: '2.0.0',
247-
sdk: 'js-server',
248-
paradigm: 'server',
249-
framework: 'nest',
250-
});
251-
expect(defaultMetadataClient.getContext()).toEqual(metadataContext);
252-
expect(domainMetadataClient.metadata).toMatchObject({
253-
domain: 'options-domain',
254-
version: '3.0.0',
255-
sdk: 'js-server',
256-
paradigm: 'server',
257-
framework: 'nest',
258-
});
259-
expect(domainMetadataClient.getContext()).toEqual(metadataContext);
260234
});
261235
});
262236

packages/shared/src/client/client.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import type { Paradigm } from '../types/paradigm';
44
export type ClientSdk = 'js-web' | 'js-server';
55
export type ClientFramework = 'react' | 'angular' | 'nest';
66

7-
export interface ClientMetadataOptions {
8-
readonly version?: string;
9-
readonly framework?: ClientFramework;
10-
}
11-
127
export interface ClientMetadata {
138
/**
149
* @deprecated alias of "domain", use domain instead

packages/web/src/client/internal/open-feature-client.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ type OpenFeatureClientOptions = {
4040
name?: string;
4141
domain?: string;
4242
version?: string;
43-
framework?: ClientFramework;
4443
};
4544

4645
/**
@@ -63,9 +62,7 @@ export class OpenFeatureClient implements Client {
6362
private readonly apiHooksAccessor: () => Hook[],
6463
private readonly globalLogger: () => Logger,
6564
private readonly options: OpenFeatureClientOptions,
66-
) {
67-
this._framework = options.framework;
68-
}
65+
) {}
6966

7067
get metadata(): ClientMetadata {
7168
return {

0 commit comments

Comments
 (0)