Skip to content

Commit d0d9a59

Browse files
committed
Store associated SDK in client metadata for use in React hooks
Signed-off-by: MattIPv4 <me@mattcowley.co.uk>
1 parent c537402 commit d0d9a59

8 files changed

Lines changed: 61 additions & 16 deletions

File tree

packages/react/src/context/use-context-mutator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type ContextMutation = {
3535
export function useContextMutator(options: ContextMutationOptions = { defaultContext: false }): ContextMutation {
3636
const { client } = useContext(Context) || {};
3737
const domain = client?.metadata.domain;
38+
const sdk = client?.metadata.sdk || OpenFeature;
3839

3940
// TODO: Replace this warning with a thrown error in a future major release,
4041
// to match the behavior of `useOpenFeatureProvider` + `useOpenFeatureClient`,
@@ -60,15 +61,14 @@ export function useContextMutator(options: ContextMutationOptions = { defaultCon
6061
async (
6162
updatedContext: EvaluationContext | ((currentContext: EvaluationContext) => EvaluationContext),
6263
): Promise<void> => {
63-
// TODO: Needs to handle `openfeature` option like OpenFeatureProvider
64-
const previousContext = OpenFeature.getContext(options?.defaultContext ? undefined : domain);
64+
const previousContext = sdk.getContext(options?.defaultContext ? undefined : domain);
6565
const resolvedContext = typeof updatedContext === 'function' ? updatedContext(previousContext) : updatedContext;
6666

6767
if (previousContext !== resolvedContext) {
6868
if (!domain || options?.defaultContext) {
69-
await OpenFeature.setContext(resolvedContext);
69+
await sdk.setContext(resolvedContext);
7070
} else {
71-
await OpenFeature.setContext(domain, resolvedContext);
71+
await sdk.setContext(domain, resolvedContext);
7272
}
7373
}
7474
},

packages/react/src/provider/provider.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type ClientOrDomain =
1515
* An instance of the OpenFeature API to use.
1616
* @see OpenFeature.getIsolated for more details.
1717
*/
18-
openfeature?: OpenFeatureAPI;
18+
sdk?: OpenFeatureAPI;
1919
client?: never;
2020
}
2121
| {
@@ -24,7 +24,7 @@ type ClientOrDomain =
2424
*/
2525
client?: Client;
2626
domain?: never;
27-
openfeature?: never;
27+
sdk?: never;
2828
};
2929

3030
type ProviderProps = {
@@ -37,8 +37,8 @@ type ProviderProps = {
3737
* @param {ProviderProps} properties props for the context provider
3838
* @returns {OpenFeatureProvider} context provider
3939
*/
40-
export function OpenFeatureProvider({ client, domain, openfeature, children, ...options }: ProviderProps) {
41-
const stableClient = React.useMemo(() => client || (openfeature ?? OpenFeature).getClient(domain), [client, domain]);
40+
export function OpenFeatureProvider({ client, domain, sdk, children, ...options }: ProviderProps) {
41+
const stableClient = React.useMemo(() => client || (sdk ?? OpenFeature).getClient(domain), [client, domain]);
4242

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

packages/react/src/provider/test-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function OpenFeatureTestProvider(testProviderOptions: TestProviderProps)
8787
const effectiveProvider = (
8888
flagValueMap ? new TestProvider(flagValueMap, testProviderOptions.delayMs) : mixInNoop(provider) || NOOP_PROVIDER
8989
) as Provider;
90-
// TODO: Needs to handle `openfeature` option like OpenFeatureProvider
90+
// TODO: Should this handle the `sdk` option like OpenFeatureProvider
9191
testProviderOptions.domain
9292
? OpenFeature.setProvider(testProviderOptions.domain, effectiveProvider)
9393
: OpenFeature.setProvider(effectiveProvider);

packages/react/src/provider/use-open-feature-provider.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ export function useOpenFeatureProvider(): Provider {
1717
throw new MissingContextError('No OpenFeature context available');
1818
}
1919

20-
// TODO: Needs to handle `openfeature` option like OpenFeatureProvider
21-
return OpenFeature.getProvider(openFeatureContext.client.metadata.domain);
20+
return (openFeatureContext.client.metadata.sdk ?? OpenFeature).getProvider(openFeatureContext.client.metadata.domain);
2221
}

packages/react/test/provider.spec.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
useStringFlagValue,
1212
} from '../src';
1313
import { TestingProvider } from './test.utils';
14+
import { useOpenFeatureProvider } from '../src/provider/use-open-feature-provider';
1415

1516
describe('OpenFeatureProvider', () => {
1617
/**
@@ -99,6 +100,43 @@ describe('OpenFeatureProvider', () => {
99100
});
100101
});
101102

103+
describe('useOpenFeatureProvider', () => {
104+
const DOMAIN = 'useOpenFeatureProvider';
105+
106+
it('should return provider from the global singleton when no SDK is specified', () => {
107+
const provider = new InMemoryProvider();
108+
OpenFeature.setProvider(DOMAIN, provider);
109+
110+
const wrapper = ({ children }: Parameters<typeof OpenFeatureProvider>[0]) => (
111+
<OpenFeatureProvider domain={DOMAIN}>{children}</OpenFeatureProvider>
112+
);
113+
114+
const { result } = renderHook(() => useOpenFeatureProvider(), { wrapper });
115+
116+
expect(result.current).toEqual(provider);
117+
});
118+
119+
it('should return provider from the specified SDK when one is provided', () => {
120+
const provider = new InMemoryProvider();
121+
OpenFeature.setProvider(DOMAIN, provider);
122+
123+
const isolatedInstance = OpenFeature.getIsolated();
124+
const isolatedProvider = new InMemoryProvider();
125+
isolatedInstance.setProvider(DOMAIN, isolatedProvider);
126+
127+
const wrapper = ({ children }: Parameters<typeof OpenFeatureProvider>[0]) => (
128+
<OpenFeatureProvider sdk={isolatedInstance} domain={DOMAIN}>
129+
{children}
130+
</OpenFeatureProvider>
131+
);
132+
133+
const { result } = renderHook(() => useOpenFeatureProvider(), { wrapper });
134+
135+
expect(result.current).toEqual(isolatedProvider);
136+
expect(result.current).not.toEqual(provider);
137+
});
138+
});
139+
102140
describe('useWhenProviderReady', () => {
103141
describe('suspendUntilReady=true (default)', () => {
104142
it('should suspend until ready and then return provider status', async () => {
@@ -165,6 +203,7 @@ describe('OpenFeatureProvider', () => {
165203
});
166204
});
167205
});
206+
168207
describe('useMutateContext', () => {
169208
const MutateButton = ({ setter }: { setter?: (prevContext: EvaluationContext) => EvaluationContext }) => {
170209
const { setContext } = useContextMutator();

packages/web/src/client/client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import type { Features } from '../evaluation';
33
import type { ProviderStatus } from '../provider';
44
import type { ProviderEvents } from '../events';
55
import type { Tracking } from '../tracking';
6+
import type { OpenFeatureAPI } from '../open-feature';
7+
8+
export interface ClientMetadataWithSDK extends ClientMetadata {
9+
readonly sdk?: OpenFeatureAPI;
10+
}
611

712
export interface Client
813
extends EvaluationLifeCycle<Client>, Features, ManageLogger<Client>, Eventing<ProviderEvents>, Tracking {
9-
readonly metadata: ClientMetadata;
14+
readonly metadata: ClientMetadataWithSDK;
1015
/**
1116
* Returns the status of the associated provider.
1217
*/

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type {
2-
ClientMetadata,
32
EvaluationContext,
43
EvaluationDetails,
54
EventHandler,
@@ -30,7 +29,8 @@ import type { InternalEventEmitter } from '../../events/internal/internal-event-
3029
import type { Hook } from '../../hooks';
3130
import type { Provider } from '../../provider';
3231
import { ProviderStatus } from '../../provider';
33-
import type { Client } from './../client';
32+
import type { Client, ClientMetadataWithSDK } from './../client';
33+
import type { OpenFeatureAPI } from '../../open-feature';
3434

3535
type OpenFeatureClientOptions = {
3636
/**
@@ -39,6 +39,7 @@ type OpenFeatureClientOptions = {
3939
name?: string;
4040
domain?: string;
4141
version?: string;
42+
sdk?: OpenFeatureAPI;
4243
};
4344

4445
/**
@@ -62,12 +63,13 @@ export class OpenFeatureClient implements Client {
6263
private readonly options: OpenFeatureClientOptions,
6364
) {}
6465

65-
get metadata(): ClientMetadata {
66+
get metadata(): ClientMetadataWithSDK {
6667
return {
6768
// Use domain if name is not provided
6869
name: this.options.domain ?? this.options.name,
6970
domain: this.options.domain ?? this.options.name,
7071
version: this.options.version,
72+
sdk: this.options.sdk,
7173
providerMetadata: this.providerAccessor().metadata,
7274
};
7375
}

packages/web/src/open-feature.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ export class OpenFeatureAPI
362362
(domain?: string) => this.getContext(domain),
363363
() => this.getHooks(),
364364
() => this._logger,
365-
{ domain, version },
365+
{ domain, version, sdk: this },
366366
);
367367
}
368368

0 commit comments

Comments
 (0)