From 4c637fcd424eb4d7df8221594a583bf73f2638cf Mon Sep 17 00:00:00 2001 From: Scott Venkataraman Date: Mon, 16 Feb 2026 14:22:06 -0500 Subject: [PATCH 1/2] `setProvider` returns a promise that's resolved when the provider is ready or is rejected if the provider fails during initialization. Remove `setProviderAndWait` Update the documentation in the readme Signed-off-by: Scott Venkataraman --- package-lock.json | 36 +++++++-- .../src/lib/feature-flag.directive.spec.ts | 2 +- .../src/lib/open-feature.module.ts | 2 - packages/react/test/evaluation.spec.tsx | 8 +- packages/react/test/tracking.spec.tsx | 4 +- packages/server/README.md | 20 ++--- packages/server/src/open-feature.ts | 40 +--------- .../src/provider/multi-provider/README.md | 4 +- packages/server/test/events.spec.ts | 10 +-- packages/server/test/hooks-data.spec.ts | 4 +- packages/server/test/open-feature.spec.ts | 8 +- packages/shared/src/open-feature.ts | 8 +- packages/web/README.md | 10 +-- packages/web/src/open-feature.ts | 78 ++----------------- .../web/src/provider/multi-provider/README.md | 4 +- packages/web/test/client.spec.ts | 24 +++--- packages/web/test/evaluation-context.spec.ts | 12 +-- packages/web/test/events.spec.ts | 20 ++--- packages/web/test/open-feature.spec.ts | 8 +- 19 files changed, 105 insertions(+), 197 deletions(-) diff --git a/package-lock.json b/package-lock.json index ad5f20f661..7f6c37f6ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3485,6 +3485,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -3527,6 +3528,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3548,6 +3550,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3569,6 +3572,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3590,6 +3594,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3611,6 +3616,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3632,6 +3638,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3653,6 +3660,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3674,6 +3682,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3695,6 +3704,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3716,6 +3726,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3737,6 +3748,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3758,6 +3770,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3777,6 +3790,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -3790,6 +3804,7 @@ "dev": true, "license": "Apache-2.0", "optional": true, + "peer": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -3801,7 +3816,8 @@ "version": "7.1.1", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -7567,6 +7583,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "prr": "~1.0.1" }, @@ -9285,6 +9302,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "bin": { "image-size": "bin/image-size.js" }, @@ -11076,6 +11094,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -11089,6 +11108,7 @@ "dev": true, "license": "ISC", "optional": true, + "peer": true, "bin": { "semver": "bin/semver" } @@ -11098,6 +11118,7 @@ "dev": true, "license": "BSD-3-Clause", "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11720,6 +11741,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "bin": { "mime": "cli.js" }, @@ -12019,6 +12041,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" @@ -12860,6 +12883,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -13125,7 +13149,8 @@ "version": "1.0.1", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/psl": { "version": "1.9.0", @@ -13692,7 +13717,8 @@ "version": "1.4.1", "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/saxes": { "version": "6.0.0", @@ -16085,7 +16111,7 @@ "@angular/cli": "^21.0.3", "@angular/common": "^21.0.4", "@angular/compiler": "^21.0.4", - "@angular/compiler-cli": "^21.1.0", + "@angular/compiler-cli": "^21.0.4", "@angular/core": "^21.0.4", "@angular/forms": "^21.0.4", "@angular/platform-browser": "^21.0.4", @@ -17666,7 +17692,7 @@ }, "packages/angular/projects/angular-sdk": { "name": "@openfeature/angular-sdk", - "version": "0.0.20", + "version": "1.0.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.0" diff --git a/packages/angular/projects/angular-sdk/src/lib/feature-flag.directive.spec.ts b/packages/angular/projects/angular-sdk/src/lib/feature-flag.directive.spec.ts index 88d4274f69..7105ff7b03 100644 --- a/packages/angular/projects/angular-sdk/src/lib/feature-flag.directive.spec.ts +++ b/packages/angular/projects/angular-sdk/src/lib/feature-flag.directive.spec.ts @@ -446,7 +446,7 @@ describe('FeatureFlagDirective', () => { }, 0, ); - await OpenFeature.setProviderAndWait(newDomain, newProvider); + await OpenFeature.setProvider(newDomain, newProvider); fixture.componentRef.setInput('domain', newDomain); await fixture.whenStable(); diff --git a/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts b/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts index 692190cd1c..cb42c44619 100644 --- a/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts +++ b/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts @@ -8,14 +8,12 @@ export interface OpenFeatureConfig { /** * The default provider to be used by OpenFeature. * If not provided, the provider can be set later using {@link OpenFeature.setProvider} - * or {@link OpenFeature.setProviderAndWait}. */ provider?: Provider; /** * A map of domain-bound providers to be registered with OpenFeature. * The key is the domain name, and the value is the provider instance. * Providers can also be registered later using {@link OpenFeature.setProvider} - * or {@link OpenFeature.setProviderAndWait}. */ domainBoundProviders?: Record; diff --git a/packages/react/test/evaluation.spec.tsx b/packages/react/test/evaluation.spec.tsx index 1fd5521de3..39b1704f6f 100644 --- a/packages/react/test/evaluation.spec.tsx +++ b/packages/react/test/evaluation.spec.tsx @@ -1030,7 +1030,7 @@ describe('evaluation', () => { } const provider = new SilentUpdateProvider({}); - await OpenFeature.setProviderAndWait('test', provider); + await OpenFeature.setProvider('test', provider); // The triggerRender prop forces a re-render const TestComponent = ({ triggerRender }: { triggerRender: number }) => { @@ -1090,7 +1090,7 @@ describe('evaluation', () => { }, }); - await OpenFeature.setProviderAndWait(EVALUATION, provider); + await OpenFeature.setProvider(EVALUATION, provider); const TestComponent = ({ flagKey }: { flagKey: string }) => { const { value } = useFlag(flagKey, 'default'); @@ -1156,7 +1156,7 @@ describe('evaluation', () => { // Async logic that CustomProvider depends upon for initialization. await new Promise((resolve) => setTimeout(resolve, 100)); - await OpenFeature.setProviderAndWait(EVALUATION, new CustomProvider()); + await OpenFeature.setProvider(EVALUATION, new CustomProvider()); })(); const TestComponent = () => { @@ -1218,7 +1218,7 @@ describe('evaluation', () => { ); beforeAll(async () => { - await OpenFeature.setProviderAndWait(DOMAIN, makeProvider()); + await OpenFeature.setProvider(DOMAIN, makeProvider()); }); afterEach(() => { diff --git a/packages/react/test/tracking.spec.tsx b/packages/react/test/tracking.spec.tsx index 4b7601165b..98ac7378de 100644 --- a/packages/react/test/tracking.spec.tsx +++ b/packages/react/test/tracking.spec.tsx @@ -30,7 +30,7 @@ describe('tracking', () => { describe('no domain', () => { it('should call default provider', async () => { const provider = mockProvider(); - await OpenFeature.setProviderAndWait(provider); + await OpenFeature.setProvider(provider); function Component() { const { track } = useTrack(); @@ -56,7 +56,7 @@ describe('tracking', () => { describe('domain set', () => { it('should call provider for domain', async () => { const domainProvider = mockProvider(); - await OpenFeature.setProviderAndWait(domain, domainProvider); + await OpenFeature.setProvider(domain, domainProvider); function Component() { const { track } = useTrack(); diff --git a/packages/server/README.md b/packages/server/README.md index 1c4efeadc5..e261dd4746 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -76,7 +76,7 @@ import { OpenFeature } from '@openfeature/server-sdk'; // Register your feature flag provider try { - await OpenFeature.setProviderAndWait(new YourProviderOfChoice()); + await OpenFeature.setProvider(new YourProviderOfChoice()); } catch (error) { console.error('Failed to initialize provider:', error); } @@ -124,18 +124,10 @@ Once you've added a provider as a dependency, it can be registered with OpenFeat #### Awaitable -To register a provider and ensure it is ready before further actions are taken, you can use the `setProviderAndWait` method as shown below: +To register a provider and ensure it is ready before further actions are taken, you can use the `setProvider` method as shown below: ```ts -await OpenFeature.setProviderAndWait(new MyProvider()); -``` - -#### Synchronous - -To register a provider in a synchronous manner, you can use the `setProvider` method as shown below: - -```ts -OpenFeature.setProvider(new MyProvider()); +await OpenFeature.setProvider(new MyProvider()); ``` Once the provider has been registered, the status can be tracked using [events](#eventing). @@ -165,7 +157,7 @@ const backupProvider = new YourBackupProvider(); const multiProvider = new MultiProvider([primaryProvider, backupProvider], new FirstMatchStrategy()); // Register the multi-provider -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); // Use as normal const client = OpenFeature.getClient(); @@ -191,7 +183,7 @@ const multiProvider = new MultiProvider( new FirstMatchStrategy(), ); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); ``` **Comparison Example:** @@ -210,7 +202,7 @@ const multiProvider = new MultiProvider( }), ); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); ``` ### Targeting diff --git a/packages/server/src/open-feature.ts b/packages/server/src/open-feature.ts index 1bc491937d..8e620b5ea1 100644 --- a/packages/server/src/open-feature.ts +++ b/packages/server/src/open-feature.ts @@ -75,7 +75,7 @@ export class OpenFeatureAPI * @returns {Promise} * @throws {Error} If the provider throws an exception during initialization. */ - setProviderAndWait(provider: Provider): Promise; + setProvider(provider: Provider): Promise; /** * Sets the provider that OpenFeature will use for flag evaluations on clients bound to the same domain. * A promise is returned that resolves when the provider is ready. @@ -85,8 +85,8 @@ export class OpenFeatureAPI * @returns {Promise} * @throws {Error} If the provider throws an exception during initialization. */ - setProviderAndWait(domain: string, provider: Provider): Promise; - async setProviderAndWait(domainOrProvider?: string | Provider, providerOrUndefined?: Provider): Promise { + setProvider(domain: string, provider: Provider): Promise; + async setProvider(domainOrProvider?: string | Provider, providerOrUndefined?: Provider): Promise { const domain = stringOrUndefined(domainOrProvider); const provider = domain ? objectOrUndefined(providerOrUndefined) @@ -95,40 +95,6 @@ export class OpenFeatureAPI await this.setAwaitableProvider(domain, provider); } - /** - * Sets the default provider for flag evaluations. - * This provider will be used by domainless clients and clients associated with domains to which no provider is bound. - * Setting a provider supersedes the current provider used in new and existing unbound clients. - * @param {Provider} provider The provider responsible for flag evaluations. - * @returns {this} OpenFeature API - */ - setProvider(provider: Provider): this; - /** - * Sets the provider for flag evaluations of providers with the given name. - * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain. - * @param {string} domain The name to identify the client - * @param {Provider} provider The provider responsible for flag evaluations. - * @returns {this} OpenFeature API - */ - setProvider(domain: string, provider: Provider): this; - setProvider(clientOrProvider?: string | Provider, providerOrUndefined?: Provider): this { - const domain = stringOrUndefined(clientOrProvider); - const provider = domain - ? objectOrUndefined(providerOrUndefined) - : objectOrUndefined(clientOrProvider); - - const maybePromise = this.setAwaitableProvider(domain, provider); - - // The setProvider method doesn't return a promise so we need to catch and - // log any errors that occur during provider initialization to avoid having - // an unhandled promise rejection. - Promise.resolve(maybePromise).catch((err) => { - this._logger.error('Error during provider initialization:', err); - }); - - return this; - } - /** * Get the default provider. * diff --git a/packages/server/src/provider/multi-provider/README.md b/packages/server/src/provider/multi-provider/README.md index aa95c6d874..7cd94d0040 100644 --- a/packages/server/src/provider/multi-provider/README.md +++ b/packages/server/src/provider/multi-provider/README.md @@ -22,7 +22,7 @@ import { OpenFeature } from '@openfeature/server-sdk'; const multiProvider = new MultiProvider([{ provider: new ProviderA() }, { provider: new ProviderB() }]); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); const client = OpenFeature.getClient(); @@ -84,7 +84,7 @@ import { MultiProvider } from '@openfeature/server-sdk'; const multiProvider = new MultiProvider([{ provider: new ProviderA() }, { provider: new ProviderB() }]); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); const client = OpenFeature.getClient(); // Tracked events will be sent to all providers by default diff --git a/packages/server/test/events.spec.ts b/packages/server/test/events.spec.ts index e3ef658725..37d4d5cbfe 100644 --- a/packages/server/test/events.spec.ts +++ b/packages/server/test/events.spec.ts @@ -536,7 +536,7 @@ describe('Events', () => { it('Ready', (done) => { const provider = new MockProvider({ hasInitialize: false }); - OpenFeature.setProviderAndWait(domain, provider).then(() => { + OpenFeature.setProvider(domain, provider).then(() => { OpenFeature.addHandler(ProviderEvents.Ready, () => { done(); }); @@ -546,7 +546,7 @@ describe('Events', () => { it('Error', (done) => { const provider = new MockProvider({ failOnInit: true }); - OpenFeature.setProviderAndWait(domain, provider).catch(() => { + OpenFeature.setProvider(domain, provider).catch(() => { OpenFeature.addHandler(ProviderEvents.Error, () => { done(); }); @@ -561,7 +561,7 @@ describe('Events', () => { const provider = new MockProvider({ hasInitialize: false }); const client = OpenFeature.getClient(domain); - OpenFeature.setProviderAndWait(domain, provider).then(() => { + OpenFeature.setProvider(domain, provider).then(() => { client.addHandler(ProviderEvents.Ready, () => { done(); }); @@ -572,7 +572,7 @@ describe('Events', () => { const provider = new MockProvider({ failOnInit: true }); const client = OpenFeature.getClient(domain); - OpenFeature.setProviderAndWait(domain, provider).catch(() => { + OpenFeature.setProvider(domain, provider).catch(() => { client.addHandler(ProviderEvents.Error, () => { done(); }); @@ -586,7 +586,7 @@ describe('Events', () => { it('provider events update status', async () => { // provider context change will take 100ms const provider = new MockProvider({ hasInitialize: false }); - OpenFeature.setProviderAndWait(domain, provider); + OpenFeature.setProvider(domain, provider); const client = OpenFeature.getClient(domain); provider.events?.emit(ProviderEvents.Stale); expect(client.providerStatus).toEqual(ProviderStatus.STALE); diff --git a/packages/server/test/hooks-data.spec.ts b/packages/server/test/hooks-data.spec.ts index 45d116413d..edaba4163b 100644 --- a/packages/server/test/hooks-data.spec.ts +++ b/packages/server/test/hooks-data.spec.ts @@ -200,7 +200,7 @@ describe('Hook Data', () => { beforeEach(async () => { OpenFeature.clearHooks(); - await OpenFeature.setProviderAndWait(MOCK_PROVIDER); + await OpenFeature.setProvider(MOCK_PROVIDER); client = OpenFeature.getClient(); }); @@ -261,7 +261,7 @@ describe('Hook Data', () => { }); it('should handle hook data in error scenarios', async () => { - await OpenFeature.setProviderAndWait(ERROR_PROVIDER); + await OpenFeature.setProvider(ERROR_PROVIDER); const hook = new TestHookWithData(); client.addHooks(hook); diff --git a/packages/server/test/open-feature.spec.ts b/packages/server/test/open-feature.spec.ts index fe41f83542..bc497dc0f7 100644 --- a/packages/server/test/open-feature.spec.ts +++ b/packages/server/test/open-feature.spec.ts @@ -117,13 +117,13 @@ describe('OpenFeature', () => { it('should not close provider if it is used by another client', async () => { const provider1 = { ...mockProvider(), onClose: jest.fn() }; - await OpenFeature.setProviderAndWait('domain1', provider1); - await OpenFeature.setProviderAndWait('domain2', provider1); + await OpenFeature.setProvider('domain1', provider1); + await OpenFeature.setProvider('domain2', provider1); - await OpenFeature.setProviderAndWait('domain1', { ...provider1 }); + await OpenFeature.setProvider('domain1', { ...provider1 }); expect(provider1.onClose).not.toHaveBeenCalled(); - await OpenFeature.setProviderAndWait('domain2', { ...provider1 }); + await OpenFeature.setProvider('domain2', { ...provider1 }); expect(provider1.onClose).toHaveBeenCalledTimes(1); }); diff --git a/packages/shared/src/open-feature.ts b/packages/shared/src/open-feature.ts index 00142b5670..3bdb20d128 100644 --- a/packages/shared/src/open-feature.ts +++ b/packages/shared/src/open-feature.ts @@ -205,17 +205,11 @@ export abstract class OpenFeatureCommonAPI< return this._apiEmitter.getHandlers(eventType); } - abstract setProviderAndWait( - clientOrProvider?: string | P, - providerContextOrUndefined?: P | EvaluationContext, - contextOrUndefined?: EvaluationContext, - ): Promise; - abstract setProvider( clientOrProvider?: string | P, providerContextOrUndefined?: P | EvaluationContext, contextOrUndefined?: EvaluationContext, - ): this; + ): Promise; protected setAwaitableProvider(domainOrProvider?: string | P, providerOrUndefined?: P): Promise | void { const domain = stringOrUndefined(domainOrProvider); diff --git a/packages/web/README.md b/packages/web/README.md index 5eba8a36f7..5a1b493ec4 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -76,7 +76,7 @@ import { OpenFeature } from '@openfeature/web-sdk'; // Register your feature flag provider try { - await OpenFeature.setProviderAndWait(new YourProviderOfChoice()); + await OpenFeature.setProvider(new YourProviderOfChoice()); } catch (error) { console.error('Failed to initialize provider:', error); } @@ -123,11 +123,11 @@ Once you've added a provider as a dependency, it can be registered with OpenFeat #### Awaitable -To register a provider and ensure it is ready before further actions are taken, you can use the `setProviderAndWait` method as shown below: +To register a provider and ensure it is ready before further actions are taken, you can use the `setProvider` method as shown below: ```ts try { - await OpenFeature.setProviderAndWait(new MyProvider()); + await OpenFeature.setProvider(new MyProvider()); } catch (error) { console.error('Failed to initialize provider:', error); } @@ -160,7 +160,7 @@ import { MultiProvider } from '@openfeature/web-sdk'; const multiProvider = new MultiProvider([{ provider: new ProviderA() }, { provider: new ProviderB() }]); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); const client = OpenFeature.getClient(); console.log(client.getBooleanDetails('my-flag', false)); @@ -211,7 +211,7 @@ sequenceDiagram In (1) the Client sends a request to the provider backend in order to get all values from all feature flags that it has. Once the provider backend replies (2) the client holds all flag values and therefore the flag evaluation process is synchronous. -In order to prevent flag evaluations from defaulting while the provider is initializing, it is highly recommended to evaluate flags only after the provider is ready. This can be done using the `setProviderAndWait` method or using the `setProvider` method and listening for the `READY` [event](#eventing). +In order to prevent flag evaluations from defaulting while the provider is initializing, it is highly recommended to evaluate flags only after the provider is ready. This can be done using the `setProvider` method or using the `setProvider` method and listening for the `READY` [event](#eventing). ### Targeting and Context diff --git a/packages/web/src/open-feature.ts b/packages/web/src/open-feature.ts index 131f8c2731..dcb03572cd 100644 --- a/packages/web/src/open-feature.ts +++ b/packages/web/src/open-feature.ts @@ -70,7 +70,7 @@ export class OpenFeatureAPI * @returns {Promise} * @throws {Error} If the provider throws an exception during initialization. */ - setProviderAndWait(provider: Provider): Promise; + setProvider(provider: Provider): Promise; /** * Sets the default provider for flag evaluations and returns a promise that resolves when the provider is ready. * This provider will be used by domainless clients and clients associated with domains to which no provider is bound. @@ -80,7 +80,7 @@ export class OpenFeatureAPI * @returns {Promise} * @throws {Error} If the provider throws an exception during initialization. */ - setProviderAndWait(provider: Provider, context: EvaluationContext): Promise; + setProvider(provider: Provider, context: EvaluationContext): Promise; /** * Sets the provider that OpenFeature will use for flag evaluations on clients bound to the same domain. * A promise is returned that resolves when the provider is ready. @@ -90,7 +90,7 @@ export class OpenFeatureAPI * @returns {Promise} * @throws {Error} If the provider throws an exception during initialization. */ - setProviderAndWait(domain: string, provider: Provider): Promise; + setProvider(domain: string, provider: Provider): Promise; /** * Sets the provider that OpenFeature will use for flag evaluations on clients bound to the same domain. * A promise is returned that resolves when the provider is ready. @@ -101,8 +101,8 @@ export class OpenFeatureAPI * @returns {Promise} * @throws {Error} If the provider throws an exception during initialization. */ - setProviderAndWait(domain: string, provider: Provider, context: EvaluationContext): Promise; - async setProviderAndWait( + setProvider(domain: string, provider: Provider, context: EvaluationContext): Promise; + async setProvider( clientOrProvider?: string | Provider, providerContextOrUndefined?: Provider | EvaluationContext, contextOrUndefined?: EvaluationContext, @@ -128,74 +128,6 @@ export class OpenFeatureAPI await this.setAwaitableProvider(domain, provider); } - /** - * Sets the default provider for flag evaluations. - * This provider will be used by domainless clients and clients associated with domains to which no provider is bound. - * Setting a provider supersedes the current provider used in new and existing unbound clients. - * @param {Provider} provider The provider responsible for flag evaluations. - * @returns {this} OpenFeature API - */ - setProvider(provider: Provider): this; - /** - * Sets the default provider and evaluation context for flag evaluations. - * This provider will be used by domainless clients and clients associated with domains to which no provider is bound. - * Setting a provider supersedes the current provider used in new and existing unbound clients. - * @param {Provider} provider The provider responsible for flag evaluations. - * @param context {EvaluationContext} The evaluation context to use for flag evaluations. - * @returns {this} OpenFeature API - */ - setProvider(provider: Provider, context: EvaluationContext): this; - /** - * Sets the provider for flag evaluations of providers with the given name. - * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain. - * @param {string} domain The name to identify the client - * @param {Provider} provider The provider responsible for flag evaluations. - * @returns {this} OpenFeature API - */ - setProvider(domain: string, provider: Provider): this; - /** - * Sets the provider and evaluation context flag evaluations of providers with the given name. - * Setting a provider supersedes the current provider used in new and existing clients bound to the same domain. - * @param {string} domain The name to identify the client - * @param {Provider} provider The provider responsible for flag evaluations. - * @param context {EvaluationContext} The evaluation context to use for flag evaluations. - * @returns {this} OpenFeature API - */ - setProvider(domain: string, provider: Provider, context: EvaluationContext): this; - setProvider( - domainOrProvider?: string | Provider, - providerContextOrUndefined?: Provider | EvaluationContext, - contextOrUndefined?: EvaluationContext, - ): this { - const domain = stringOrUndefined(domainOrProvider); - const provider = domain - ? objectOrUndefined(providerContextOrUndefined) - : objectOrUndefined(domainOrProvider); - const context = domain - ? objectOrUndefined(contextOrUndefined) - : objectOrUndefined(providerContextOrUndefined); - - if (context) { - // synonymously setting context prior to provider initialization. - // No context change event will be emitted. - if (domain) { - this._domainScopedContext.set(domain, context); - } else { - this._context = context; - } - } - - const maybePromise = this.setAwaitableProvider(domain, provider); - - // The setProvider method doesn't return a promise so we need to catch and - // log any errors that occur during provider initialization to avoid having - // an unhandled promise rejection. - Promise.resolve(maybePromise).catch((err) => { - this._logger.error('Error during provider initialization:', err); - }); - return this; - } - /** * Get the default provider. * diff --git a/packages/web/src/provider/multi-provider/README.md b/packages/web/src/provider/multi-provider/README.md index d7f4dba3eb..d203f88677 100644 --- a/packages/web/src/provider/multi-provider/README.md +++ b/packages/web/src/provider/multi-provider/README.md @@ -22,7 +22,7 @@ import { OpenFeature } from '@openfeature/web-sdk'; const multiProvider = new MultiProvider([{ provider: new ProviderA() }, { provider: new ProviderB() }]); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); const client = OpenFeature.getClient(); @@ -146,7 +146,7 @@ import { OpenFeature } from '@openfeature/web-sdk'; const multiProvider = new MultiProvider([{ provider: new ProviderA() }, { provider: new ProviderB() }]); -await OpenFeature.setProviderAndWait(multiProvider); +await OpenFeature.setProvider(multiProvider); const client = OpenFeature.getClient(); // Tracked events will be sent to all providers by default diff --git a/packages/web/test/client.spec.ts b/packages/web/test/client.spec.ts index 24837624de..53c5509a69 100644 --- a/packages/web/test/client.spec.ts +++ b/packages/web/test/client.spec.ts @@ -148,7 +148,7 @@ describe('OpenFeatureClient', () => { const provider = new mockAsyncProvider(false); expect(provider.status).toBe(ProviderStatus.NOT_READY); - await OpenFeature.setProviderAndWait(provider); + await OpenFeature.setProvider(provider); expect(provider.status).toBe(ProviderStatus.READY); expect(spy).toHaveBeenCalled(); }); @@ -158,7 +158,7 @@ describe('OpenFeatureClient', () => { const provider = new mockAsyncProvider(true); expect(provider.status).toBe(ProviderStatus.NOT_READY); - await expect(OpenFeature.setProviderAndWait(provider)).rejects.toThrow(); + await expect(OpenFeature.setProvider(provider)).rejects.toThrow(); expect(provider.status).toBe(ProviderStatus.ERROR); expect(spy).toHaveBeenCalled(); }); @@ -400,7 +400,7 @@ describe('OpenFeatureClient', () => { }, } as unknown as Provider; it('status must be READY if init resolves', async () => { - await OpenFeature.setProviderAndWait('1.7.1, 1.7.3', initProvider); + await OpenFeature.setProvider('1.7.1, 1.7.3', initProvider); const client = OpenFeature.getClient('1.7.1, 1.7.3'); expect(client.providerStatus).toEqual(ProviderStatus.READY); }); @@ -416,7 +416,7 @@ describe('OpenFeatureClient', () => { }, } as unknown as Provider; it('status must be ERROR if init rejects', async () => { - await expect(OpenFeature.setProviderAndWait('1.7.4', errorProvider)).rejects.toThrow(); + await expect(OpenFeature.setProvider('1.7.4', errorProvider)).rejects.toThrow(); const client = OpenFeature.getClient('1.7.4'); expect(client.providerStatus).toEqual(ProviderStatus.ERROR); }); @@ -432,7 +432,7 @@ describe('OpenFeatureClient', () => { }, } as unknown as Provider; it('must short circuit and return PROVIDER_FATAL code if provider FATAL', async () => { - await expect(OpenFeature.setProviderAndWait('1.7.5, 1.7.6, 1.7.8', fatalProvider)).rejects.toThrow(); + await expect(OpenFeature.setProvider('1.7.5, 1.7.6, 1.7.8', fatalProvider)).rejects.toThrow(); const client = OpenFeature.getClient('1.7.5, 1.7.6, 1.7.8'); expect(client.providerStatus).toEqual(ProviderStatus.FATAL); @@ -455,7 +455,7 @@ describe('OpenFeatureClient', () => { }, } as unknown as Provider; it('must short circuit and return PROVIDER_NOT_READY code if provider NOT_READY', async () => { - OpenFeature.setProviderAndWait('1.7.7', neverReadyProvider).catch(() => { + OpenFeature.setProvider('1.7.7', neverReadyProvider).catch(() => { // do nothing }); const defaultVal = 'default'; @@ -617,7 +617,7 @@ describe('OpenFeatureClient', () => { describe('providerStatus', () => { it('should return current provider status', (done) => { - OpenFeature.setProviderAndWait({ + OpenFeature.setProvider({ ...MOCK_PROVIDER, initialize: () => { return new Promise((resolve) => setTimeout(resolve, 1000)); @@ -631,7 +631,7 @@ describe('OpenFeatureClient', () => { }); it('should return READY if initialize not defined', async () => { - await OpenFeature.setProviderAndWait({ ...MOCK_PROVIDER, initialize: undefined }); + await OpenFeature.setProvider({ ...MOCK_PROVIDER, initialize: undefined }); expect(OpenFeature.getClient().providerStatus).toEqual(ProviderStatus.READY); }); }); @@ -647,7 +647,7 @@ describe('OpenFeatureClient', () => { const contextValue = 'val'; it('should no-op and not throw if tracking not defined on provider', async () => { - await OpenFeature.setProviderAndWait({ ...MOCK_PROVIDER, track: undefined }); + await OpenFeature.setProvider({ ...MOCK_PROVIDER, track: undefined }); const client = OpenFeature.getClient(); expect(() => { @@ -656,7 +656,7 @@ describe('OpenFeatureClient', () => { }); it('provide empty tracking details to provider if not supplied in call', async () => { - await OpenFeature.setProviderAndWait({ ...MOCK_PROVIDER }); + await OpenFeature.setProvider({ ...MOCK_PROVIDER }); const client = OpenFeature.getClient(); client.track(eventName); @@ -664,7 +664,7 @@ describe('OpenFeatureClient', () => { }); it('should no-op and not throw if provider throws', async () => { - await OpenFeature.setProviderAndWait({ + await OpenFeature.setProvider({ ...MOCK_PROVIDER, track: () => { throw new Error('fake error'); @@ -678,7 +678,7 @@ describe('OpenFeatureClient', () => { }); it('should call provider with correct context', async () => { - await OpenFeature.setProviderAndWait({ ...MOCK_PROVIDER }); + await OpenFeature.setProvider({ ...MOCK_PROVIDER }); await OpenFeature.setContext({ [contextKey]: contextValue }); const client = OpenFeature.getClient(); client.track(eventName, trackingDetails); diff --git a/packages/web/test/evaluation-context.spec.ts b/packages/web/test/evaluation-context.spec.ts index 288301c88f..3c051086f4 100644 --- a/packages/web/test/evaluation-context.spec.ts +++ b/packages/web/test/evaluation-context.spec.ts @@ -83,7 +83,7 @@ describe('Evaluation Context', () => { it('should set the context for the default provider prior to initialization', async () => { const context: EvaluationContext = { property1: false }; const provider = new MockProvider(); - await OpenFeature.setProviderAndWait(provider, context); + await OpenFeature.setProvider(provider, context); expect(initializeMock).toHaveBeenCalledWith(context); expect(OpenFeature.getContext()).toEqual(context); }); @@ -92,7 +92,7 @@ describe('Evaluation Context', () => { const context: EvaluationContext = { property1: false }; const domain = 'test'; const provider = new MockProvider({ name: domain }); - await OpenFeature.setProviderAndWait(domain, provider, context); + await OpenFeature.setProvider(domain, provider, context); expect(OpenFeature.getContext()).toEqual({}); expect(OpenFeature.getContext(domain)).toEqual(context); expect(initializeMock).toHaveBeenCalledWith(context); @@ -126,8 +126,8 @@ describe('Evaluation Context', () => { const defaultProvider = new MockProvider(); const provider1 = new MockProvider(); - await OpenFeature.setProviderAndWait(defaultProvider); - await OpenFeature.setProviderAndWait(domain, provider1); + await OpenFeature.setProvider(defaultProvider); + await OpenFeature.setProvider(domain, provider1); // Spy on context changed handlers of both providers const defaultProviderSpy = jest.spyOn(defaultProvider, 'onContextChange'); @@ -148,8 +148,8 @@ describe('Evaluation Context', () => { const defaultProvider = new MockProvider(); const provider1 = new MockProvider(); - await OpenFeature.setProviderAndWait(defaultProvider); - await OpenFeature.setProviderAndWait(domain, provider1); + await OpenFeature.setProvider(defaultProvider); + await OpenFeature.setProvider(domain, provider1); // Spy on boolean resolvers of both providers const defaultProviderSpy = jest.spyOn(defaultProvider, 'resolveBooleanEvaluation'); diff --git a/packages/web/test/events.spec.ts b/packages/web/test/events.spec.ts index 3229bde0a3..e70e689b5d 100644 --- a/packages/web/test/events.spec.ts +++ b/packages/web/test/events.spec.ts @@ -563,7 +563,7 @@ describe('Events', () => { it('Ready', (done) => { const provider = new MockProvider({ hasInitialize: false }); - OpenFeature.setProviderAndWait(domain, provider).then(() => { + OpenFeature.setProvider(domain, provider).then(() => { OpenFeature.addHandler(ProviderEvents.Ready, () => { done(); }); @@ -573,7 +573,7 @@ describe('Events', () => { it('Error', (done) => { const provider = new MockProvider({ failOnInit: true }); - OpenFeature.setProviderAndWait(domain, provider).catch(() => { + OpenFeature.setProvider(domain, provider).catch(() => { OpenFeature.addHandler(ProviderEvents.Error, () => { done(); }); @@ -588,7 +588,7 @@ describe('Events', () => { const provider = new MockProvider({ hasInitialize: false }); const client = OpenFeature.getClient(domain); - OpenFeature.setProviderAndWait(domain, provider).then(() => { + OpenFeature.setProvider(domain, provider).then(() => { client.addHandler(ProviderEvents.Ready, () => { done(); }); @@ -599,7 +599,7 @@ describe('Events', () => { const provider = new MockProvider({ failOnInit: true }); const client = OpenFeature.getClient(domain); - OpenFeature.setProviderAndWait(domain, provider).catch(() => { + OpenFeature.setProvider(domain, provider).catch(() => { client.addHandler(ProviderEvents.Error, () => { done(); }); @@ -617,7 +617,7 @@ describe('Events', () => { const handler = jest.fn(() => {}); - await OpenFeature.setProviderAndWait(domain, provider); + await OpenFeature.setProvider(domain, provider); OpenFeature.addHandler(ProviderEvents.Reconciling, handler); OpenFeature.addHandler(ProviderEvents.ContextChanged, handler); await OpenFeature.setContext(domain, {}); @@ -635,7 +635,7 @@ describe('Events', () => { const reconcileHandler = jest.fn(() => {}); const changedEventHandler = jest.fn(() => {}); - await OpenFeature.setProviderAndWait(domain, provider); + await OpenFeature.setProvider(domain, provider); OpenFeature.addHandler(ProviderEvents.Reconciling, reconcileHandler); OpenFeature.addHandler(ProviderEvents.ContextChanged, changedEventHandler); await OpenFeature.setContext(domain, {}); @@ -651,7 +651,7 @@ describe('Events', () => { const handler = jest.fn(() => {}); - await OpenFeature.setProviderAndWait(domain, provider); + await OpenFeature.setProvider(domain, provider); OpenFeature.addHandler(ProviderEvents.Reconciling, handler); // this should not be called OpenFeature.addHandler(ProviderEvents.ContextChanged, handler); await OpenFeature.setContext(domain, {}); @@ -669,7 +669,7 @@ describe('Events', () => { const handler = jest.fn(() => {}); const client = OpenFeature.getClient(domain); - await OpenFeature.setProviderAndWait(domain, provider); + await OpenFeature.setProvider(domain, provider); client.addHandler(ProviderEvents.Reconciling, handler); client.addHandler(ProviderEvents.ContextChanged, handler); await OpenFeature.setContext(domain, {}); @@ -685,7 +685,7 @@ describe('Events', () => { const handler = jest.fn(() => {}); const client = OpenFeature.getClient(domain); - await OpenFeature.setProviderAndWait(domain, provider); + await OpenFeature.setProvider(domain, provider); client.addHandler(ProviderEvents.Reconciling, handler); // this should not be called client.addHandler(ProviderEvents.ContextChanged, handler); await OpenFeature.setContext(domain, {}); @@ -869,7 +869,7 @@ describe('Events', () => { it('provider events update status', async () => { // provider context change will take 100ms const provider = new MockProvider({ hasInitialize: false }); - OpenFeature.setProviderAndWait(domain, provider); + OpenFeature.setProvider(domain, provider); const client = OpenFeature.getClient(domain); provider.events?.emit(ProviderEvents.Stale); expect(client.providerStatus).toEqual(ProviderStatus.STALE); diff --git a/packages/web/test/open-feature.spec.ts b/packages/web/test/open-feature.spec.ts index 308d8f43b4..62ef0c2b0f 100644 --- a/packages/web/test/open-feature.spec.ts +++ b/packages/web/test/open-feature.spec.ts @@ -118,13 +118,13 @@ describe('OpenFeature', () => { it('should not close provider if it is used by another client', async () => { const provider1 = { ...mockProvider(), onClose: jest.fn() }; - await OpenFeature.setProviderAndWait('domain1', provider1); - await OpenFeature.setProviderAndWait('domain2', provider1); + await OpenFeature.setProvider('domain1', provider1); + await OpenFeature.setProvider('domain2', provider1); - await OpenFeature.setProviderAndWait('domain1', { ...provider1 }); + await OpenFeature.setProvider('domain1', { ...provider1 }); expect(provider1.onClose).not.toHaveBeenCalled(); - await OpenFeature.setProviderAndWait('domain2', { ...provider1 }); + await OpenFeature.setProvider('domain2', { ...provider1 }); expect(provider1.onClose).toHaveBeenCalledTimes(1); }); From 3d74ff0130ae088964fc293e78e29fd59014520e Mon Sep 17 00:00:00 2001 From: Scott Venkataraman Date: Mon, 16 Feb 2026 16:13:44 -0500 Subject: [PATCH 2/2] Updates to address PR feedback -Grammatical edit to packages/web/README.md -Add catch to async call in angular module, as well as replace .map with .forEach. Signed-off-by: Scott Venkataraman --- .../angular-sdk/src/lib/open-feature.module.ts | 12 +++++++++--- packages/web/README.md | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts b/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts index cb42c44619..fce323320d 100644 --- a/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts +++ b/packages/angular/projects/angular-sdk/src/lib/open-feature.module.ts @@ -36,11 +36,17 @@ export const OPEN_FEATURE_CONFIG_TOKEN = new InjectionToken(' export class OpenFeatureModule { static forRoot(config: OpenFeatureConfig): ModuleWithProviders { const context = typeof config.context === 'function' ? config.context() : config.context; - OpenFeature.setProvider(config.provider, context); + if (config.provider) { + OpenFeature.setProvider(config.provider, context).catch((err) => { + console.error('Error setting default provider in OpenFeatureModule:', err); + }); + } if (config.domainBoundProviders) { - Object.entries(config.domainBoundProviders).map(([domain, provider]) => - OpenFeature.setProvider(domain, provider, context), + Object.entries(config.domainBoundProviders).forEach(([domain, provider]) => + OpenFeature.setProvider(domain, provider, context).catch((err) => { + console.error(`Error setting provider for domain "${domain}" in OpenFeatureModule:`, err); + }), ); } diff --git a/packages/web/README.md b/packages/web/README.md index 5a1b493ec4..6007b74cf4 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -211,7 +211,7 @@ sequenceDiagram In (1) the Client sends a request to the provider backend in order to get all values from all feature flags that it has. Once the provider backend replies (2) the client holds all flag values and therefore the flag evaluation process is synchronous. -In order to prevent flag evaluations from defaulting while the provider is initializing, it is highly recommended to evaluate flags only after the provider is ready. This can be done using the `setProvider` method or using the `setProvider` method and listening for the `READY` [event](#eventing). +In order to prevent flag evaluations from defaulting while the provider is initializing, it is highly recommended to evaluate flags only after the provider is ready. This can be done using the `setProvider` method and listening for the `READY` [event](#eventing). ### Targeting and Context