-
Notifications
You must be signed in to change notification settings - Fork 59
feat: Add isolated (non-singleton) OpenFeature API instances #1325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import type { Client } from '@openfeature/web-sdk'; | ||
| import type { Client, OpenFeatureAPIBase } from '@openfeature/web-sdk'; | ||
| import { OpenFeature } from '@openfeature/web-sdk'; | ||
| import * as React from 'react'; | ||
| import type { ReactFlagEvaluationOptions } from '../options'; | ||
|
|
@@ -11,6 +11,28 @@ type ClientOrDomain = | |
| * @see OpenFeature.setProvider() and overloads. | ||
| */ | ||
| domain?: string; | ||
| /** | ||
| * An isolated OpenFeature API instance to use instead of the global singleton. | ||
| * Use this in micro-frontend architectures where different parts of the application | ||
| * need isolated OpenFeature instances. | ||
| * @see createIsolatedOpenFeatureAPI from '@openfeature/web-sdk/isolated' | ||
| * @example | ||
| * ```tsx | ||
| * import { createIsolatedOpenFeatureAPI } from '@openfeature/web-sdk/isolated'; | ||
| * | ||
| * const MyOpenFeature = createIsolatedOpenFeatureAPI(); | ||
| * MyOpenFeature.setProvider(myProvider); | ||
| * | ||
| * function App() { | ||
| * return ( | ||
| * <OpenFeatureProvider openfeature={MyOpenFeature}> | ||
| * {children} | ||
| * </OpenFeatureProvider> | ||
| * ); | ||
| * } | ||
| * ``` | ||
| */ | ||
| openfeature?: OpenFeatureAPIBase; | ||
| client?: never; | ||
| } | ||
| | { | ||
|
|
@@ -19,6 +41,7 @@ type ClientOrDomain = | |
| */ | ||
| client?: Client; | ||
| domain?: never; | ||
| openfeature?: never; | ||
| }; | ||
|
|
||
| type ProviderProps = { | ||
|
|
@@ -31,8 +54,11 @@ type ProviderProps = { | |
| * @param {ProviderProps} properties props for the context provider | ||
| * @returns {OpenFeatureProvider} context provider | ||
| */ | ||
| export function OpenFeatureProvider({ client, domain, children, ...options }: ProviderProps) { | ||
| const stableClient = React.useMemo(() => client || OpenFeature.getClient(domain), [client, domain]); | ||
| export function OpenFeatureProvider({ client, domain, openfeature, children, ...options }: ProviderProps) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will see if there is a way to have a separate React package like the other SDKs. |
||
| const stableClient = React.useMemo( | ||
| () => client || (openfeature ?? OpenFeature).getClient(domain), | ||
| [client, domain, openfeature], | ||
| ); | ||
|
|
||
| return <Context.Provider value={{ client: stableClient, options, domain }}>{children}</Context.Provider>; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // This config rolls up all the types for the isolated module to a single declaration (d.ts) file. | ||
|
|
||
| import dts from 'rollup-plugin-dts'; | ||
|
|
||
| export default { | ||
| input: './src/isolated.ts', | ||
| output: { | ||
| file: './dist/isolated.d.ts', | ||
| format: 'es', | ||
| }, | ||
| // function indicating which deps should be considered external: external deps will NOT have their types bundled | ||
| external: (id) => { | ||
| // bundle everything except peer deps (@openfeature/*, @nest/*, react, rxjs) | ||
| return id.startsWith('@openfeature') || id.startsWith('@nest') || id === 'rxjs' || id === 'react'; | ||
| }, | ||
| plugins: [ | ||
| // use the rollup override tsconfig (applies equivalent in each sub-packages as well) | ||
| dts({ tsconfig: './tsconfig.rollup.json', respectExternal: true }), | ||
| ], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ export * from './client'; | |
| export * from './provider'; | ||
| export * from './evaluation'; | ||
| export * from './open-feature'; | ||
| export * from './open-feature-base'; | ||
|
||
| export * from './transaction-context'; | ||
| export * from './events'; | ||
| export * from './hooks'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||||
| /** | ||||||||
| * @module @openfeature/server-sdk/isolated | ||||||||
| * Provides non-singleton OpenFeature API instances for testing and multi-tenant scenarios. | ||||||||
| * WARNING: This module provides non-singleton instances that do NOT share state | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this warning could be a little stronger/clearer |
||||||||
| * with the global OpenFeature singleton. Only use this when isolation is explicitly required. | ||||||||
| * @example | ||||||||
| * ```typescript | ||||||||
| * import { createIsolatedOpenFeatureAPI } from '@openfeature/server-sdk/isolated'; | ||||||||
| * | ||||||||
| * const MyOpenFeature = createIsolatedOpenFeatureAPI(); | ||||||||
| * MyOpenFeature.setProvider(myProvider); | ||||||||
| * const client = MyOpenFeature.getClient(); | ||||||||
| * ``` | ||||||||
| */ | ||||||||
|
|
||||||||
| import { OpenFeatureAPIBase } from './open-feature-base'; | ||||||||
|
|
||||||||
| /** | ||||||||
| * An isolated (non-singleton) OpenFeature API instance. | ||||||||
| * This class is NOT exported from the main entry point. | ||||||||
|
||||||||
| * This class is NOT exported from the main entry point. | |
| * This concrete implementation class is not exported from the main entry point; | |
| * only the base {@link OpenFeatureAPIBase} is exported there. |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OpenFeatureIsolatedAPIImpl constructor doesn't add any functionality beyond calling super(). Consider removing this unnecessary constructor to simplify the code. TypeScript will implicitly call the parent constructor when no constructor is defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I right that this
openfeatureprop should ONLY be used when someone is doing the advanced isolated stuff? If so I think we should add that clearly in the docstring - "WARNING: this is only required for special cases where an isolated instance is needed. If you don't know what that means, you should not be using this prop".We could also call it
isolatedOpenFeatureto make it clearer that it's a special-case thing, not a generally-used thing.