From d16ebc839773ffffb54bba31be99f0328af9eff4 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Feb 2026 07:30:15 -0500 Subject: [PATCH 1/3] feat: add isolated instance Signed-off-by: Todd Baert --- specification.json | 42 +++++++++++ specification/glossary.md | 8 +++ specification/sections/01-flag-evaluation.md | 75 ++++++++++++++++++++ 3 files changed, 125 insertions(+) diff --git a/specification.json b/specification.json index 0f45dabc..edaffded 100644 --- a/specification.json +++ b/specification.json @@ -365,6 +365,48 @@ "RFC 2119 keyword": "MUST", "children": [] }, + { + "id": "Requirement 1.8.1", + "machine_id": "requirement_1_8_1", + "content": "The `API` MUST expose a factory function which creates and returns a new, independent instance of the `API`.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 1.8.2", + "machine_id": "requirement_1_8_2", + "content": "Instances returned by the factory function MUST conform to the same `API` contract as the global singleton, including flag evaluation, provider management, context, hooks, events, and shutdown functionality.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 1.8.3", + "machine_id": "requirement_1_8_3", + "content": "The factory function for creating isolated instances SHOULD be housed in a distinct module, import path, package, or namespace from the global singleton `API`.", + "RFC 2119 keyword": "SHOULD", + "children": [] + }, + { + "id": "Requirement 1.8.4", + "machine_id": "requirement_1_8_4", + "content": "A `provider` instance MUST NOT be registered with more than one `API` instance simultaneously.", + "RFC 2119 keyword": "MUST NOT", + "children": [] + }, + { + "id": "Requirement 1.8.5", + "machine_id": "requirement_1_8_5", + "content": "The `provider mutator` MUST indicate an error if the specified `provider` instance is already bound to a different `API` instance.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 1.8.6", + "machine_id": "requirement_1_8_6", + "content": "A `provider` MUST be unbound from an `API` instance when it is no longer registered with that instance.", + "RFC 2119 keyword": "MUST", + "children": [] + }, { "id": "Requirement 2.1.1", "machine_id": "requirement_2_1_1", diff --git a/specification/glossary.md b/specification/glossary.md index 3b97a2f9..43a53d86 100644 --- a/specification/glossary.md +++ b/specification/glossary.md @@ -31,6 +31,7 @@ This document defines some terms that are used across this specification. - [Provider](#provider) - [Provider Lifecycle](#provider-lifecycle) - [Domain](#domain) + - [Isolated API Instance](#isolated-api-instance) - [Integration](#integration) - [Evaluation Context](#evaluation-context) - [Transaction Context Propagator](#transaction-context-propagator) @@ -125,6 +126,13 @@ The possible states and transitions of a provider over the course of its usage, An identifier which logically binds clients with providers, allowing for multiple providers to be used simultaneously within a single application. Domain binding is dynamic; it may change over the course of an application's lifetime (i.e.: a client associated with the default provider via an unbound domain will be bound to a new provider if a provider is subsequently assigned to that domain). +### Isolated API Instance + +An independent, non-singleton instance of the [Feature Flag API](#feature-flag-api) created via a factory function. +Each isolated instance maintains its own state, including providers, evaluation context, hooks, and event handlers. +Isolated instances do not share state with the global singleton or with each other. +Intended for advanced use cases such as micro-frontend architectures, dependency injection frameworks, and testing scenarios. + ### Integration An SDK-compliant secondary function that is abstracted by the Feature Flag API, and requires only minimal configuration by the Application Author. Examples include telemetry, tracking, custom logging and monitoring. diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 807677bf..779baac3 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -530,3 +530,78 @@ see: [error codes](../types.md#error-code) > The client's `provider status` accessor **MUST** indicate `NOT_READY` once the `shutdown` function of the associated provider terminates. Regardless of the success of the provider's `shutdown` function, the `provider status` should convey the provider is no longer ready to use once the shutdown function terminates. + +### 1.8. Isolated API Instances + +[![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) + +While the `API` [functions as a global singleton](#requirement-111) in the default case, certain use cases require independent instances of the `API` with fully isolated state. Examples include micro-frontend architectures (where separately developed applications coexist in a single runtime), dependency injection frameworks and IoC containers (which manage object lifecycles outside the singleton's scope), testing scenarios (where parallel tests mutating shared state can interfere with each other), and server-side applications composed of multiple submodules each requiring distinct providers. + +#### Requirement 1.8.1 + +> The `API` **MUST** expose a factory function which creates and returns a new, independent instance of the `API`. + +Each instance returned by this factory function maintains its own state, including providers, `evaluation context`, hooks, event handlers, and transaction context propagators. +Instances created by the factory function do not share state with the "default" global singleton or with each other. + +```java +// example factory function +OpenFeatureAPI isolated = createIsolatedOpenFeatureAPI(); +isolated.setProvider(new MyProvider()); +Client client = isolated.getClient(); +``` + +See [application integrator](../glossary.md#application-integrator), [isolated API instance](../glossary.md#isolated-api-instance) for details. + +#### Requirement 1.8.2 + +> Instances returned by the factory function **MUST** conform to the same `API` contract as the global singleton, including flag evaluation, provider management, context, hooks, events, and shutdown functionality. + +An isolated `API` instance is functionally equivalent to the global singleton, but with independent state. +Application code which uses a `client` obtained from an isolated instance behaves identically to code using a `client` from the global singleton. + +#### Requirement 1.8.3 + +> The factory function for creating isolated instances **SHOULD** be housed in a distinct module, import path, package, or namespace from the global singleton `API`. + +The factory function should be intentionally less discoverable than the default singleton, reducing the risk of `application authors` inadvertently creating isolated instances when the singleton would be more appropriate. +The distinct import path serves as an explicit signal that the consumer is opting into advanced, non-default behavior. + +```typescript +// example: the factory function is accessed from a distinct module +import { createIsolatedOpenFeatureAPI } from '@openfeature/web-sdk/isolated'; +``` + +#### Requirement 1.8.4 + +> A `provider` instance **MUST NOT** be registered with more than one `API` instance simultaneously. + +Because the `API` instance manages the [lifecycle](./02-providers.md) of its associated providers (including initialization, shutdown, and event handling), binding a `provider` to more than one `API` instance could result in undefined behavior. A `provider` instance **MAY** be registered with multiple `domains` within a single `API` instance. + +See [setting a provider](#setting-a-provider), [domain](../glossary.md#domain) for details. + +#### Requirement 1.8.5 + +> The `provider mutator` **MUST** indicate an error if the specified `provider` instance is already bound to a different `API` instance. + +Implementations indicate an error in a manner idiomatic to the language in use (returning an error, throwing an exception, etc). + +```java +// example: registering a provider already bound to another instance results in an error +OpenFeatureAPI isolatedA = createIsolatedOpenFeatureAPI(); +OpenFeatureAPI isolatedB = createIsolatedOpenFeatureAPI(); + +Provider myProvider = new MyProvider(); +isolatedA.setProvider(myProvider); +isolatedB.setProvider(myProvider); // error: provider is already bound to a different API instance +``` + +#### Requirement 1.8.6 + +> A `provider` **MUST** be unbound from an `API` instance when it is no longer registered with that instance. + +A `provider` is no longer registered when it has been replaced (by setting a new `provider` for the same `domain`), when `clearProviders` has been called, or when the `API`'s `shutdown` function has been called. +Providers which remain registered under other `domains` within the same `API` instance are not unbound. +Once unbound, a `provider` instance may be registered with any `API` instance. + +See [shutdown](#16-shutdown), [setting a provider](#setting-a-provider) for details. From 2eba22a448d06cdf8d1b2201267a9803f6445668 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 26 Feb 2026 07:34:59 -0500 Subject: [PATCH 2/3] fixup: toc Signed-off-by: Todd Baert --- specification/glossary.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/specification/glossary.md b/specification/glossary.md index 43a53d86..68d0c7c9 100644 --- a/specification/glossary.md +++ b/specification/glossary.md @@ -128,10 +128,7 @@ An identifier which logically binds clients with providers, allowing for multipl ### Isolated API Instance -An independent, non-singleton instance of the [Feature Flag API](#feature-flag-api) created via a factory function. -Each isolated instance maintains its own state, including providers, evaluation context, hooks, and event handlers. -Isolated instances do not share state with the global singleton or with each other. -Intended for advanced use cases such as micro-frontend architectures, dependency injection frameworks, and testing scenarios. +An independent, non-singleton instance of the [Feature Flag API](#feature-flag-api) created via a factory function. Each isolated instance maintains its own state, including providers, evaluation context, hooks, and event handlers. Isolated instances do not share state with the global singleton or with each other. Intended for advanced use cases such as micro-frontend architectures, dependency injection frameworks, and testing scenarios. ### Integration From d48f002524c4ae282a5f0dbba2cd2f79b7990e04 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 3 Mar 2026 11:13:40 -0500 Subject: [PATCH 3/3] fixup: relaxed 1.8.4, removed 1.8.5, 1.8.6 Signed-off-by: Todd Baert --- specification.json | 18 ++--------- specification/sections/01-flag-evaluation.md | 32 +++----------------- 2 files changed, 6 insertions(+), 44 deletions(-) diff --git a/specification.json b/specification.json index edaffded..99093209 100644 --- a/specification.json +++ b/specification.json @@ -389,22 +389,8 @@ { "id": "Requirement 1.8.4", "machine_id": "requirement_1_8_4", - "content": "A `provider` instance MUST NOT be registered with more than one `API` instance simultaneously.", - "RFC 2119 keyword": "MUST NOT", - "children": [] - }, - { - "id": "Requirement 1.8.5", - "machine_id": "requirement_1_8_5", - "content": "The `provider mutator` MUST indicate an error if the specified `provider` instance is already bound to a different `API` instance.", - "RFC 2119 keyword": "MUST", - "children": [] - }, - { - "id": "Requirement 1.8.6", - "machine_id": "requirement_1_8_6", - "content": "A `provider` MUST be unbound from an `API` instance when it is no longer registered with that instance.", - "RFC 2119 keyword": "MUST", + "content": "A `provider` instance SHOULD NOT be registered with more than one `API` instance simultaneously.", + "RFC 2119 keyword": "SHOULD NOT", "children": [] }, { diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 779baac3..a7b9a20e 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -574,34 +574,10 @@ import { createIsolatedOpenFeatureAPI } from '@openfeature/web-sdk/isolated'; #### Requirement 1.8.4 -> A `provider` instance **MUST NOT** be registered with more than one `API` instance simultaneously. +> A `provider` instance **SHOULD NOT** be registered with more than one `API` instance simultaneously. -Because the `API` instance manages the [lifecycle](./02-providers.md) of its associated providers (including initialization, shutdown, and event handling), binding a `provider` to more than one `API` instance could result in undefined behavior. A `provider` instance **MAY** be registered with multiple `domains` within a single `API` instance. +Because the `API` instance manages the [lifecycle](./02-providers.md) of its associated providers (including initialization, shutdown, and event handling), binding a `provider` to more than one `API` instance could result in undefined behavior. +A `provider` instance can be registered with multiple `domains` within a single `API` instance. +When a provider is no longer associated with an `API` instance, it can be registered to another. See [setting a provider](#setting-a-provider), [domain](../glossary.md#domain) for details. - -#### Requirement 1.8.5 - -> The `provider mutator` **MUST** indicate an error if the specified `provider` instance is already bound to a different `API` instance. - -Implementations indicate an error in a manner idiomatic to the language in use (returning an error, throwing an exception, etc). - -```java -// example: registering a provider already bound to another instance results in an error -OpenFeatureAPI isolatedA = createIsolatedOpenFeatureAPI(); -OpenFeatureAPI isolatedB = createIsolatedOpenFeatureAPI(); - -Provider myProvider = new MyProvider(); -isolatedA.setProvider(myProvider); -isolatedB.setProvider(myProvider); // error: provider is already bound to a different API instance -``` - -#### Requirement 1.8.6 - -> A `provider` **MUST** be unbound from an `API` instance when it is no longer registered with that instance. - -A `provider` is no longer registered when it has been replaced (by setting a new `provider` for the same `domain`), when `clearProviders` has been called, or when the `API`'s `shutdown` function has been called. -Providers which remain registered under other `domains` within the same `API` instance are not unbound. -Once unbound, a `provider` instance may be registered with any `API` instance. - -See [shutdown](#16-shutdown), [setting a provider](#setting-a-provider) for details.