-
Notifications
You must be signed in to change notification settings - Fork 59
feat: lock provider binding to a single API instance #1342
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 | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,13 @@ import type { Paradigm } from './types'; | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| type AnyProviderStatus = ClientProviderStatus | ServerProviderStatus; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Well-known Symbol used to track which {@link OpenFeatureCommonAPI} instance a provider is currently bound to. | ||||||||||||||||||||||||||
| * The symbol is shared across module boundaries and globally unique in the runtime. | ||||||||||||||||||||||||||
| * @internal | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| export const BOUND_API_KEY = Symbol.for('@openfeature/js-sdk/provider/bound-api'); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * A provider and its current status. | ||||||||||||||||||||||||||
| * For internal use only. | ||||||||||||||||||||||||||
|
|
@@ -217,6 +224,21 @@ export abstract class OpenFeatureCommonAPI< | |||||||||||||||||||||||||
| contextOrUndefined?: EvaluationContext, | ||||||||||||||||||||||||||
| ): this; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private bindProvider(provider: P): void { | ||||||||||||||||||||||||||
| Object.defineProperty(provider, BOUND_API_KEY, { | ||||||||||||||||||||||||||
| value: this, | ||||||||||||||||||||||||||
| configurable: true, | ||||||||||||||||||||||||||
| enumerable: false, | ||||||||||||||||||||||||||
| writable: false, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+227
to
+234
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. This way is a transparent implementation to the providers. |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private unbindProvider(provider: P): void { | ||||||||||||||||||||||||||
| if (BOUND_API_KEY in provider) { | ||||||||||||||||||||||||||
| delete provider[BOUND_API_KEY]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| protected setAwaitableProvider(domainOrProvider?: string | P, providerOrUndefined?: P): Promise<void> | void { | ||||||||||||||||||||||||||
| const domain = stringOrUndefined(domainOrProvider); | ||||||||||||||||||||||||||
| const provider = objectOrUndefined<P>(domainOrProvider) ?? objectOrUndefined<P>(providerOrUndefined); | ||||||||||||||||||||||||||
|
|
@@ -241,6 +263,12 @@ export abstract class OpenFeatureCommonAPI< | |||||||||||||||||||||||||
| throw new GeneralError(`Provider '${provider.metadata.name}' is intended for use on the ${provider.runsOn}.`); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (BOUND_API_KEY in provider && provider[BOUND_API_KEY] !== this) { | ||||||||||||||||||||||||||
| throw new GeneralError( | ||||||||||||||||||||||||||
| `Provider '${provider.metadata.name}' is already bound to a different OpenFeature API instance. A provider instance must not be registered with multiple API instances simultaneously.`, | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const emitters = this.getAssociatedEventEmitters(domain); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let initializationPromise: Promise<void> | void = undefined; | ||||||||||||||||||||||||||
|
|
@@ -300,10 +328,13 @@ export abstract class OpenFeatureCommonAPI< | |||||||||||||||||||||||||
| this._defaultProvider = wrappedProvider; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| this.bindProvider(provider); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| this.transferListeners(oldProvider, provider, domain, emitters); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Do not close a provider that is bound to any client | ||||||||||||||||||||||||||
| if (!this.allProviders.includes(oldProvider)) { | ||||||||||||||||||||||||||
| this.unbindProvider(oldProvider); | ||||||||||||||||||||||||||
| oldProvider?.onClose?.()?.catch((err: Error | undefined) => { | ||||||||||||||||||||||||||
| this._logger.error(`error closing provider: ${err?.message}, ${err?.stack}`); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
@@ -399,6 +430,8 @@ export abstract class OpenFeatureCommonAPI< | |||||||||||||||||||||||||
| await this?._defaultProvider.provider?.onClose?.(); | ||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||
| this.handleShutdownError(this._defaultProvider.provider, err); | ||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||
| this.unbindProvider(this._defaultProvider.provider); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const wrappers = Array.from(this._domainScopedProviders); | ||||||||||||||||||||||||||
|
|
@@ -409,6 +442,8 @@ export abstract class OpenFeatureCommonAPI< | |||||||||||||||||||||||||
| await wrapper?.provider.onClose?.(); | ||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||
| this.handleShutdownError(wrapper?.provider, err); | ||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||
| this.unbindProvider(wrapper?.provider); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
442
to
447
Contributor
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. The optional chaining on
Suggested change
|
||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
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.
Do we have to move this to an extra internal file as we do with
OpenFeatureClientimplementations?This is only exported for the tests.