Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions packages/react/src/provider/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Client } from '@openfeature/web-sdk';
import type { Client, OpenFeatureAPIBase } from '@openfeature/web-sdk';
import { OpenFeature } from '@openfeature/web-sdk';
import * as React from 'react';
import type { ReactFlagEvaluationOptions } from '../options';
Expand All @@ -11,6 +11,28 @@ type ClientOrDomain =
* @see OpenFeature.setProvider() and overloads.
*/
domain?: string;
/**
* An isolated OpenFeature API instance to use instead of the global singleton.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Am I right that this openfeature prop should ONLY be used when someone is doing the advanced isolated stuff? If so I think we should add that clearly in the docstring - "WARNING: this is only required for special cases where an isolated instance is needed. If you don't know what that means, you should not be using this prop".

We could also call it isolatedOpenFeature to make it clearer that it's a special-case thing, not a generally-used thing.

* Use this in micro-frontend architectures where different parts of the application
* need isolated OpenFeature instances.
* @see createIsolatedOpenFeatureAPI from '@openfeature/web-sdk/isolated'
* @example
* ```tsx
* import { createIsolatedOpenFeatureAPI } from '@openfeature/web-sdk/isolated';
*
* const MyOpenFeature = createIsolatedOpenFeatureAPI();
* MyOpenFeature.setProvider(myProvider);
*
* function App() {
* return (
* <OpenFeatureProvider openfeature={MyOpenFeature}>
* {children}
* </OpenFeatureProvider>
* );
* }
* ```
*/
openfeature?: OpenFeatureAPIBase;
client?: never;
}
| {
Expand All @@ -19,6 +41,7 @@ type ClientOrDomain =
*/
client?: Client;
domain?: never;
openfeature?: never;
};

type ProviderProps = {
Expand All @@ -31,8 +54,11 @@ type ProviderProps = {
* @param {ProviderProps} properties props for the context provider
* @returns {OpenFeatureProvider} context provider
*/
export function OpenFeatureProvider({ client, domain, children, ...options }: ProviderProps) {
const stableClient = React.useMemo(() => client || OpenFeature.getClient(domain), [client, domain]);
export function OpenFeatureProvider({ client, domain, openfeature, children, ...options }: ProviderProps) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I will see if there is a way to have a separate React package like the other SDKs.

const stableClient = React.useMemo(
() => client || (openfeature ?? OpenFeature).getClient(domain),
[client, domain, openfeature],
);

return <Context.Provider value={{ client: stableClient, options, domain }}>{children}</Context.Provider>;
}
21 changes: 16 additions & 5 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@
"dist/"
],
"exports": {
"types": "./dist/types.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"default": "./dist/cjs/index.js"
".": {
"types": "./dist/types.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"default": "./dist/cjs/index.js"
},
"./isolated": {
"types": "./dist/isolated.d.ts",
"import": "./dist/esm/isolated.js",
"require": "./dist/cjs/isolated.js",
"default": "./dist/cjs/isolated.js"
}
},
"types": "./dist/types.d.ts",
"scripts": {
Expand All @@ -19,9 +27,12 @@
"lint:fix": "eslint ./ --fix",
"clean": "shx rm -rf ./dist",
"build:esm": "esbuild src/index.ts --bundle --external:@openfeature/core --sourcemap --target=es2015 --platform=node --format=esm --outfile=./dist/esm/index.js --analyze",
"build:esm-isolated": "esbuild src/isolated.ts --bundle --external:@openfeature/core --sourcemap --target=es2015 --platform=node --format=esm --outfile=./dist/esm/isolated.js --analyze",
"build:cjs": "esbuild src/index.ts --bundle --external:@openfeature/core --sourcemap --target=es2015 --platform=node --format=cjs --outfile=./dist/cjs/index.js --analyze",
"build:cjs-isolated": "esbuild src/isolated.ts --bundle --external:@openfeature/core --sourcemap --target=es2015 --platform=node --format=cjs --outfile=./dist/cjs/isolated.js --analyze",
"build:rollup-types": "rollup -c ../../rollup.config.mjs",
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:rollup-types",
"build:rollup-types-isolated": "rollup -c ./rollup.isolated.config.mjs",
"build": "npm run clean && npm run build:esm && npm run build:esm-isolated && npm run build:cjs && npm run build:cjs-isolated && npm run build:rollup-types && npm run build:rollup-types-isolated",
"postbuild": "shx cp ./../../package.esm.json ./dist/esm/package.json",
"current-version": "echo $npm_package_version",
"prepack": "shx cp ./../../LICENSE ./LICENSE",
Expand Down
20 changes: 20 additions & 0 deletions packages/server/rollup.isolated.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This config rolls up all the types for the isolated module to a single declaration (d.ts) file.

import dts from 'rollup-plugin-dts';

export default {
input: './src/isolated.ts',
output: {
file: './dist/isolated.d.ts',
format: 'es',
},
// function indicating which deps should be considered external: external deps will NOT have their types bundled
external: (id) => {
// bundle everything except peer deps (@openfeature/*, @nest/*, react, rxjs)
return id.startsWith('@openfeature') || id.startsWith('@nest') || id === 'rxjs' || id === 'react';
},
plugins: [
// use the rollup override tsconfig (applies equivalent in each sub-packages as well)
dts({ tsconfig: './tsconfig.rollup.json', respectExternal: true }),
],
};
1 change: 1 addition & 0 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './client';
export * from './provider';
export * from './evaluation';
export * from './open-feature';
export * from './open-feature-base';
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The OpenFeatureAPIBase class is being exported from the main entry point (packages/server/src/index.ts), which exposes implementation details that should remain internal. This contradicts the PR's stated goal of keeping isolated instances accessible only via the /isolated sub-path.

While the protected constructor prevents direct instantiation, exporting the base class unnecessarily expands the public API surface and could lead to confusion or misuse. Consider removing this export to keep the base class as an internal implementation detail.

Copilot uses AI. Check for mistakes.
export * from './transaction-context';
export * from './events';
export * from './hooks';
Expand Down
57 changes: 57 additions & 0 deletions packages/server/src/isolated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @module @openfeature/server-sdk/isolated
* Provides non-singleton OpenFeature API instances for testing and multi-tenant scenarios.
* WARNING: This module provides non-singleton instances that do NOT share state
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this warning could be a little stronger/clearer

* with the global OpenFeature singleton. Only use this when isolation is explicitly required.
* @example
* ```typescript
* import { createIsolatedOpenFeatureAPI } from '@openfeature/server-sdk/isolated';
*
* const MyOpenFeature = createIsolatedOpenFeatureAPI();
* MyOpenFeature.setProvider(myProvider);
* const client = MyOpenFeature.getClient();
* ```
*/

import { OpenFeatureAPIBase } from './open-feature-base';

/**
* An isolated (non-singleton) OpenFeature API instance.
* This class is NOT exported from the main entry point.
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The comment states "This class is NOT exported from the main entry point" but this is inaccurate. While the OpenFeatureIsolatedAPIImpl class itself is not exported, its base class OpenFeatureAPIBase is exported from the main entry point in index.ts. This comment could be misleading. Consider updating it to reflect the actual visibility or removing the export of OpenFeatureAPIBase from the main entry point.

Suggested change
* This class is NOT exported from the main entry point.
* This concrete implementation class is not exported from the main entry point;
* only the base {@link OpenFeatureAPIBase} is exported there.

Copilot uses AI. Check for mistakes.
* @internal
*/
class OpenFeatureIsolatedAPIImpl extends OpenFeatureAPIBase {
constructor() {
super();
}
}
Comment thread
jonathannorris marked this conversation as resolved.
Comment on lines +23 to +27
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

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

The OpenFeatureIsolatedAPIImpl constructor doesn't add any functionality beyond calling super(). Consider removing this unnecessary constructor to simplify the code. TypeScript will implicitly call the parent constructor when no constructor is defined.

Copilot uses AI. Check for mistakes.

/**
* Creates a new isolated OpenFeature API instance.
* Each instance has its own providers, context, hooks, event handlers, and transaction context propagator.
* State is NOT shared with the global singleton or other isolated instances.
* @returns {OpenFeatureIsolatedAPI} A new isolated OpenFeature API instance
* @example
* ```typescript
* import { createIsolatedOpenFeatureAPI } from '@openfeature/server-sdk/isolated';
*
* // Create an isolated instance for testing
* const TestOpenFeature = createIsolatedOpenFeatureAPI();
*
* // Configure it independently of the global singleton
* TestOpenFeature.setProvider(mockProvider);
* TestOpenFeature.setContext({ environment: 'test' });
*
* // Get a client from the isolated instance
* const client = TestOpenFeature.getClient();
* ```
*/
export function createIsolatedOpenFeatureAPI(): OpenFeatureIsolatedAPI {
return new OpenFeatureIsolatedAPIImpl();
}

/**
* Type alias for an isolated OpenFeature API instance.
* This is the same interface as the base OpenFeature API.
*/
export type OpenFeatureIsolatedAPI = OpenFeatureAPIBase;
Loading