Skip to content

Comments

feat: lock provider binding to a single API instance#1342

Draft
lukas-reining wants to merge 1 commit intomainfrom
feat/provider-api-locking
Draft

feat: lock provider binding to a single API instance#1342
lukas-reining wants to merge 1 commit intomainfrom
feat/provider-api-locking

Conversation

@lukas-reining
Copy link
Member

This PR

Once we implement non-singleton OpenFeature APIs as discussed in open-feature/spec#359, we need to make sure a single provider is only ever assigned to one API instance at a time. This is necessary due to the API instance managing the lifecycle of the provider.

This PR drafts a mechanism to track and enforce the association between provider instances and OpenFeatureAPI instances, preventing a provider from being registered with multiple APIs at the same time.
It does this by using a symbol to "bind" a provider to an API instance and ensures proper cleanup (unbinding) when providers are replaced or the API is shut down.

This change is transparent to the provider implementations.

Related Issues

open-feature/spec#359

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @lukas-reining, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a critical feature to enforce that an OpenFeature provider instance is exclusively bound to a single OpenFeature API instance at any given time. This change is essential for managing provider lifecycles effectively, especially in environments where multiple API instances might exist. The implementation uses a symbolic key for binding and includes robust error handling and cleanup procedures, all while remaining transparent to provider implementations.

Highlights

  • Provider Binding Mechanism: Introduced a mechanism to bind a provider instance to a single OpenFeature API instance using a globally unique BOUND_API_KEY symbol. This prevents a provider from being registered with multiple API instances simultaneously.
  • Enforced Single API Association: Implemented validation that throws a GeneralError if an attempt is made to register a provider that is already bound to a different API instance. This ensures proper lifecycle management of providers.
  • Automatic Unbinding and Cleanup: Ensured providers are automatically unbound from an API instance when they are replaced, or when the API is shut down via clearProviders or close methods. This prevents resource leaks and maintains correct state.
  • Enhanced Testability: Modified the OpenFeatureAPI constructors in both server and web packages from private to protected to facilitate subclassing for testing purposes, enabling more comprehensive unit tests for the new binding logic.
  • Comprehensive Test Coverage: Added extensive unit tests to cover various scenarios of provider binding, unbinding, and conflict detection, ensuring the robustness of the new mechanism.
Changelog
  • packages/server/src/open-feature.ts
    • Changed the OpenFeatureAPI constructor visibility from private to protected to allow subclassing.
  • packages/server/test/open-feature.spec.ts
    • Introduced TestOpenFeatureAPI and getBoundApi helper functions for testing.
    • Added a new test suite for provider binding requirements, covering various scenarios like binding conflicts, unbinding on replacement, and cleanup during API shutdown.
  • packages/shared/src/open-feature.ts
    • Exported BOUND_API_KEY symbol for tracking provider-API instance binding.
    • Implemented bindProvider and unbindProvider private methods to manage the association.
    • Added validation in setAwaitableProvider to prevent a provider from being bound to multiple API instances.
    • Ensured providers are bound upon being set and unbound upon replacement or API shutdown.
  • packages/web/src/open-feature.ts
    • Changed the OpenFeatureAPI constructor visibility from private to protected to allow subclassing.
  • packages/web/test/open-feature.spec.ts
    • Introduced TestOpenFeatureAPI and getBoundApi helper functions for testing.
    • Added a new test suite for provider binding requirements, covering various scenarios like binding conflicts, unbinding on replacement, and cleanup during API shutdown.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

/**
* 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
Copy link
Member Author

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 OpenFeatureClient implementations?
This is only exported for the tests.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism to lock a provider instance to a single OpenFeature API instance, which is an important step towards supporting non-singleton APIs. The implementation uses a well-known Symbol to bind providers to an API instance and correctly handles unbinding when providers are replaced or the API is shut down. The changes are well-contained within the shared OpenFeatureCommonAPI and are accompanied by a comprehensive set of tests for both server and web packages. The approach is transparent to provider implementations, which is great.

I have one suggestion to improve code clarity regarding some redundant optional chaining. Overall, this is a solid contribution.

Comment on lines 442 to 447
await wrapper?.provider.onClose?.();
} catch (err) {
this.handleShutdownError(wrapper?.provider, err);
} finally {
this.unbindProvider(wrapper?.provider);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The optional chaining on wrapper seems unnecessary. wrapper is destructured from an array of _domainScopedProviders entries and should not be nullable. The provider property on ProviderWrapper is also not nullable. Using wrapper.provider directly would improve clarity and type safety.

Suggested change
await wrapper?.provider.onClose?.();
} catch (err) {
this.handleShutdownError(wrapper?.provider, err);
} finally {
this.unbindProvider(wrapper?.provider);
}
await wrapper.provider.onClose?.();
} catch (err) {
this.handleShutdownError(wrapper.provider, err);
} finally {
this.unbindProvider(wrapper.provider);
}

Comment on lines +227 to +234
private bindProvider(provider: P): void {
Object.defineProperty(provider, BOUND_API_KEY, {
value: this,
configurable: true,
enumerable: false,
writable: false,
});
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way is a transparent implementation to the providers.
We can not directly add it to the provider interface "non-breaking" as it only is an interface.

@lukas-reining lukas-reining force-pushed the feat/provider-api-locking branch from 6fb07fc to 1aab336 Compare February 16, 2026 09:03
@lukas-reining lukas-reining force-pushed the feat/provider-api-locking branch from 1aab336 to f60c147 Compare February 16, 2026 09:04
@lukas-reining lukas-reining requested review from a team, beeme1mr and toddbaert February 18, 2026 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant