Skip to content

Commit ccc8bee

Browse files
committed
Provide a local singleton of OpenFeatureAPI
Signed-off-by: MattIPv4 <[email protected]>
1 parent 9b05be9 commit ccc8bee

File tree

1 file changed

+61
-5
lines changed

1 file changed

+61
-5
lines changed

packages/web/src/open-feature.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type DomainRecord = {
2828
};
2929

3030
const _globalThis = globalThis as OpenFeatureGlobal;
31+
const _localThis = {} as OpenFeatureGlobal;
3132

3233
export class OpenFeatureAPI
3334
extends OpenFeatureCommonAPI<ClientProviderStatus, Provider, Hook>
@@ -50,16 +51,19 @@ export class OpenFeatureAPI
5051
/**
5152
* Gets a singleton instance of the OpenFeature API.
5253
* @ignore
54+
* @param {boolean} global Whether to get the global (window) singleton instance or a package-local singleton instance.
5355
* @returns {OpenFeatureAPI} OpenFeature API
5456
*/
55-
static getInstance(): OpenFeatureAPI {
56-
const globalApi = _globalThis[GLOBAL_OPENFEATURE_API_KEY];
57+
static getInstance(global = true): OpenFeatureAPI {
58+
const store = global ? _globalThis : _localThis;
59+
60+
const globalApi = store[GLOBAL_OPENFEATURE_API_KEY];
5761
if (globalApi) {
5862
return globalApi;
5963
}
6064

6165
const instance = new OpenFeatureAPI();
62-
_globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
66+
store[GLOBAL_OPENFEATURE_API_KEY] = instance;
6367
return instance;
6468
}
6569

@@ -430,8 +434,60 @@ export class OpenFeatureAPI
430434
}
431435
}
432436

437+
interface OpenFeatureAPIWithIsolated extends OpenFeatureAPI {
438+
/**
439+
* An package-local singleton instance of the OpenFeature API.
440+
*
441+
* By default, the OpenFeature API is exposed as a global singleton (stored on `window` in browsers) instance.
442+
* While this can be very convenient as domains, providers, etc., are shared across an entire application,
443+
* this can mean that in multi-frontend architectures (e.g. micro-frontends) different parts of an application
444+
* can think they're loading different versions of OpenFeature, when they're actually all sharing the same instance.
445+
*
446+
* The `isolated` property provides access to a package-local singleton instance of the OpenFeature API,
447+
* which is not shared globally, isolated from the global singleton.
448+
* This allows different parts of an application to have their own isolated OpenFeature API instances,
449+
* avoiding potential conflicts and ensuring they're using the expected version of the SDK.
450+
*
451+
* HOWEVER, the `isolated` instance is *isolated* to the package, and so will not share domains, providers, etc., with
452+
* the global singleton instance. As it is still a singleton within the package though, it will share state with other
453+
* uses of the `isolated` instance imported from the same package within the same micro-frontend.
454+
* @example
455+
* import { OpenFeature } from '@openfeature/web-sdk';
456+
*
457+
* OpenFeature.setProvider(new MyGlobalProvider()); // Sets the provider for the default domain on the global instance
458+
* OpenFeature.isolated.setProvider(new MyIsolatedProvider()); // Sets the provider for the default domain on the isolated instance
459+
*
460+
* const globalClient = OpenFeature.getClient(); // Uses MyGlobalProvider, the provider for the default domain on the global instance
461+
* const isolatedClient = OpenFeature.isolated.getClient(); // Uses MyIsolatedProvider, the provider for the default domain on the isolated instance
462+
*
463+
* // In the same micro-frontend, in a different file ...
464+
* import { OpenFeature } from '@openfeature/web-sdk';
465+
*
466+
* const globalClient = OpenFeature.getClient(); // Uses MyGlobalProvider, the provider for the default domain on the global instance
467+
* const isolatedClient = OpenFeature.isolated.getClient(); // Uses MyIsolatedProvider, the provider for the default domain on the isolated instance
468+
*
469+
* // In another micro-frontend, after the above has executed ...
470+
* import { OpenFeature } from '@openfeature/web-sdk';
471+
*
472+
* const globalClient = OpenFeature.getClient(); // Uses MyGlobalProvider, the provider for the default domain on the global instance
473+
* const isolatedClient = OpenFeature.isolated.getClient(); // Returns the NOOP provider, as this is a different isolated instance
474+
*/
475+
readonly isolated: OpenFeatureAPI;
476+
}
477+
478+
const createOpenFeatureAPI = (): OpenFeatureAPIWithIsolated => {
479+
const globalInstance = OpenFeatureAPI.getInstance();
480+
const localInstance = OpenFeatureAPI.getInstance(false);
481+
482+
return Object.assign(globalInstance, {
483+
get isolated() {
484+
return localInstance;
485+
},
486+
});
487+
};
488+
433489
/**
434490
* A singleton instance of the OpenFeature API.
435-
* @returns {OpenFeatureAPI} OpenFeature API
491+
* @returns {OpenFeatureAPIWithIsolated} OpenFeature API
436492
*/
437-
export const OpenFeature = OpenFeatureAPI.getInstance();
493+
export const OpenFeature = createOpenFeatureAPI();

0 commit comments

Comments
 (0)