@openfeature/react-sdk 0.0.5-experimental → 0.0.6-experimental
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -12
- package/dist/cjs/index.js +83 -7
- package/dist/cjs/index.js.map +2 -2
- package/dist/esm/index.js +87 -8
- package/dist/esm/index.js.map +2 -2
- package/dist/types.d.ts +1047 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,24 +24,36 @@
|
|
|
24
24
|
|
|
25
25
|
[OpenFeature](https://openfeature.dev) is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool.
|
|
26
26
|
|
|
27
|
-
🧪 This
|
|
27
|
+
🧪 This SDK is experimental.
|
|
28
28
|
|
|
29
|
+
## Basic Usage
|
|
29
30
|
|
|
30
|
-
Here's a basic example of how
|
|
31
|
+
Here's a basic example of how to use the current API with the in-memory provider:
|
|
31
32
|
|
|
32
|
-
```
|
|
33
|
+
```tsx
|
|
33
34
|
import logo from './logo.svg';
|
|
34
35
|
import './App.css';
|
|
35
36
|
import { OpenFeatureProvider, useFeatureFlag, OpenFeature } from '@openfeature/react-sdk';
|
|
36
37
|
import { FlagdWebProvider } from '@openfeature/flagd-web-provider';
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
const flagConfig = {
|
|
40
|
+
'new-message': {
|
|
41
|
+
disabled: false,
|
|
42
|
+
variants: {
|
|
43
|
+
on: true,
|
|
44
|
+
off: false,
|
|
45
|
+
},
|
|
46
|
+
defaultVariant: "on",
|
|
47
|
+
contextEvaluator: (context: EvaluationContext) => {
|
|
48
|
+
if (context.silly) {
|
|
49
|
+
return 'on';
|
|
50
|
+
}
|
|
51
|
+
return 'off'
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
OpenFeature.setProvider(new InMemoryProvider(flagConfig));
|
|
45
57
|
|
|
46
58
|
function App() {
|
|
47
59
|
return (
|
|
@@ -52,12 +64,12 @@ function App() {
|
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
function Page() {
|
|
55
|
-
const booleanFlag = useFeatureFlag('new-
|
|
67
|
+
const booleanFlag = useFeatureFlag('new-message', false);
|
|
56
68
|
return (
|
|
57
69
|
<div className="App">
|
|
58
70
|
<header className="App-header">
|
|
59
71
|
<img src={logo} className="App-logo" alt="logo" />
|
|
60
|
-
{booleanFlag ? <p>Welcome to this OpenFeature-enabled React app!</p> : <p>Welcome to this React app.</p>}
|
|
72
|
+
{booleanFlag.value ? <p>Welcome to this OpenFeature-enabled React app!</p> : <p>Welcome to this React app.</p>}
|
|
61
73
|
</header>
|
|
62
74
|
</div>
|
|
63
75
|
)
|
|
@@ -65,3 +77,95 @@ function Page() {
|
|
|
65
77
|
|
|
66
78
|
export default App;
|
|
67
79
|
```
|
|
80
|
+
|
|
81
|
+
### Multiple Providers and Scoping
|
|
82
|
+
|
|
83
|
+
Multiple providers and scoped clients can be configured by passing a `clientName` to the `OpenFeatureProvider`:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Flags within this scope will use the a client/provider associated with `myClient`,
|
|
87
|
+
function App() {
|
|
88
|
+
return (
|
|
89
|
+
<OpenFeatureProvider clientName={'myClient'}>
|
|
90
|
+
<Page></Page>
|
|
91
|
+
</OpenFeatureProvider>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This is analogous to:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
OpenFeature.getClient('myClient');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Re-rendering with Context Changes
|
|
103
|
+
|
|
104
|
+
By default, if the OpenFeature [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) is modified, components will be re-rendered.
|
|
105
|
+
This is useful in cases where flag values are dependant on user-attributes or other application state (user logged in, items in card, etc).
|
|
106
|
+
You can disable this feature in the `useFeatureFlag` hook options:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
function Page() {
|
|
110
|
+
const booleanFlag = useFeatureFlag('new-message', false, { updateOnContextChanged: false });
|
|
111
|
+
return (
|
|
112
|
+
<MyComponents></MyComponents>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For more information about how evaluation context works in the React SDK, see the documentation on OpenFeature's [static context SDK paradigm](https://openfeature.dev/specification/glossary/#static-context-paradigm).
|
|
118
|
+
|
|
119
|
+
### Re-rendering with Flag Configuration Changes
|
|
120
|
+
|
|
121
|
+
By default, if the underlying provider emits a `ConfigurationChanged` event, components will be re-rendered.
|
|
122
|
+
This is useful if you want your UI to immediately reflect changes in the backend flag configuration.
|
|
123
|
+
You can disable this feature in the `useFeatureFlag` hook options:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
function Page() {
|
|
127
|
+
const booleanFlag = useFeatureFlag('new-message', false, { updateOnConfigurationChanged: false });
|
|
128
|
+
return (
|
|
129
|
+
<MyComponents></MyComponents>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Note that if your provider doesn't support updates, this configuration has no impact.
|
|
135
|
+
|
|
136
|
+
### Suspense Support
|
|
137
|
+
|
|
138
|
+
Frequently, providers need to perform some initial startup tasks.
|
|
139
|
+
It may be desireable not to display components with feature flags until this is complete.
|
|
140
|
+
Built-in [suspense](https://react.dev/reference/react/Suspense) support makes this easy:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
function Content() {
|
|
144
|
+
// cause the "fallback" to be displayed if the component uses feature flags and the provider is not ready
|
|
145
|
+
return (
|
|
146
|
+
<Suspense fallback={<Fallback />}>
|
|
147
|
+
<Message />
|
|
148
|
+
</Suspense>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function Message() {
|
|
153
|
+
// component to render after READY.
|
|
154
|
+
const { value: showNewMessage } = useFeatureFlag('new-message', false);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<>
|
|
158
|
+
{showNewMessage ? (
|
|
159
|
+
<p>Welcome to this OpenFeature-enabled React app!</p>
|
|
160
|
+
) : (
|
|
161
|
+
<p>Welcome to this plain old React app!</p>
|
|
162
|
+
)}
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function Fallback() {
|
|
168
|
+
// component to render before READY.
|
|
169
|
+
return <p>Waiting for provider to be ready...</p>;
|
|
170
|
+
}
|
|
171
|
+
```
|
package/dist/cjs/index.js
CHANGED
|
@@ -3,8 +3,22 @@ var __create = Object.create;
|
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
8
22
|
var __export = (target, all) => {
|
|
9
23
|
for (var name in all)
|
|
10
24
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -45,9 +59,9 @@ var import_react = require("react");
|
|
|
45
59
|
var React = __toESM(require("react"));
|
|
46
60
|
var import_web_sdk = require("@openfeature/web-sdk");
|
|
47
61
|
var Context = React.createContext(void 0);
|
|
48
|
-
var OpenFeatureProvider = ({ client, children }) => {
|
|
62
|
+
var OpenFeatureProvider = ({ client, clientName, children }) => {
|
|
49
63
|
if (!client) {
|
|
50
|
-
client = import_web_sdk.OpenFeature.getClient();
|
|
64
|
+
client = import_web_sdk.OpenFeature.getClient(clientName);
|
|
51
65
|
}
|
|
52
66
|
return /* @__PURE__ */ React.createElement(Context.Provider, { value: client }, children);
|
|
53
67
|
};
|
|
@@ -62,15 +76,34 @@ var useOpenFeatureClient = () => {
|
|
|
62
76
|
};
|
|
63
77
|
|
|
64
78
|
// src/use-feature-flag.ts
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
var DEFAULT_OPTIONS = {
|
|
80
|
+
updateOnContextChanged: true,
|
|
81
|
+
updateOnConfigurationChanged: true,
|
|
82
|
+
suspend: true
|
|
83
|
+
};
|
|
84
|
+
function useFeatureFlag(flagKey, defaultValue, options) {
|
|
85
|
+
const defaultedOptions = __spreadValues(__spreadValues({}, DEFAULT_OPTIONS), options);
|
|
86
|
+
const [, updateState] = (0, import_react.useState)();
|
|
87
|
+
const forceUpdate = () => {
|
|
88
|
+
updateState({});
|
|
89
|
+
};
|
|
67
90
|
const client = useOpenFeatureClient();
|
|
68
91
|
(0, import_react.useEffect)(() => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
if (client.providerStatus !== import_web_sdk2.ProviderStatus.READY) {
|
|
93
|
+
client.addHandler(import_web_sdk2.ProviderEvents.Ready, forceUpdate);
|
|
94
|
+
if (defaultedOptions.suspend) {
|
|
95
|
+
suspend(client, updateState);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (defaultedOptions.updateOnContextChanged) {
|
|
99
|
+
client.addHandler(import_web_sdk2.ProviderEvents.ContextChanged, forceUpdate);
|
|
100
|
+
}
|
|
101
|
+
if (defaultedOptions.updateOnConfigurationChanged) {
|
|
102
|
+
client.addHandler(import_web_sdk2.ProviderEvents.ConfigurationChanged, forceUpdate);
|
|
103
|
+
}
|
|
72
104
|
return () => {
|
|
73
105
|
client.removeHandler(import_web_sdk2.ProviderEvents.Ready, forceUpdate);
|
|
106
|
+
client.removeHandler(import_web_sdk2.ProviderEvents.ContextChanged, forceUpdate);
|
|
74
107
|
client.removeHandler(import_web_sdk2.ProviderEvents.ConfigurationChanged, forceUpdate);
|
|
75
108
|
};
|
|
76
109
|
}, [client]);
|
|
@@ -87,6 +120,49 @@ function getFlag(client, flagKey, defaultValue) {
|
|
|
87
120
|
return client.getObjectDetails(flagKey, defaultValue);
|
|
88
121
|
}
|
|
89
122
|
}
|
|
123
|
+
function suspend(client, updateState) {
|
|
124
|
+
let suspendResolver;
|
|
125
|
+
let suspendRejecter;
|
|
126
|
+
const suspendPromise = new Promise((resolve) => {
|
|
127
|
+
suspendResolver = () => {
|
|
128
|
+
resolve();
|
|
129
|
+
client.removeHandler(import_web_sdk2.ProviderEvents.Ready, suspendResolver);
|
|
130
|
+
};
|
|
131
|
+
suspendRejecter = () => {
|
|
132
|
+
resolve();
|
|
133
|
+
client.removeHandler(import_web_sdk2.ProviderEvents.Error, suspendRejecter);
|
|
134
|
+
};
|
|
135
|
+
client.addHandler(import_web_sdk2.ProviderEvents.Ready, suspendResolver);
|
|
136
|
+
client.addHandler(import_web_sdk2.ProviderEvents.Error, suspendRejecter);
|
|
137
|
+
});
|
|
138
|
+
updateState(suspenseWrapper(suspendPromise));
|
|
139
|
+
}
|
|
140
|
+
function suspenseWrapper(promise) {
|
|
141
|
+
let status = 0 /* Pending */;
|
|
142
|
+
let result;
|
|
143
|
+
const suspended = promise.then(
|
|
144
|
+
(value) => {
|
|
145
|
+
status = 1 /* Success */;
|
|
146
|
+
result = value;
|
|
147
|
+
},
|
|
148
|
+
(error) => {
|
|
149
|
+
status = 2 /* Error */;
|
|
150
|
+
result = error;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
return () => {
|
|
154
|
+
switch (status) {
|
|
155
|
+
case 0 /* Pending */:
|
|
156
|
+
throw suspended;
|
|
157
|
+
case 1 /* Success */:
|
|
158
|
+
return result;
|
|
159
|
+
case 2 /* Error */:
|
|
160
|
+
throw result;
|
|
161
|
+
default:
|
|
162
|
+
throw new Error("Suspending promise is in an unknown state.");
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
90
166
|
|
|
91
167
|
// src/index.ts
|
|
92
168
|
__reExport(src_exports, require("@openfeature/web-sdk"), module.exports);
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts", "../../src/use-feature-flag.ts", "../../src/provider.tsx"],
|
|
4
|
-
"sourcesContent": ["export * from './use-feature-flag';\nexport * from './provider';\n// re-export the web-sdk so consumers can access that API from the react-sdk\nexport * from '@openfeature/web-sdk';\n", "import { Client, EvaluationDetails, FlagValue, ProviderEvents } from '@openfeature/web-sdk';\nimport { useEffect, useState } from 'react';\nimport { useOpenFeatureClient } from './provider';\n\nexport function useFeatureFlag<T extends FlagValue>(flagKey: string, defaultValue: T): EvaluationDetails<T> {\n const [,
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["export * from './use-feature-flag';\nexport * from './provider';\n// re-export the web-sdk so consumers can access that API from the react-sdk\nexport * from '@openfeature/web-sdk';\n", "import { Client, EvaluationDetails, FlagEvaluationOptions, FlagValue, ProviderEvents, ProviderStatus } from '@openfeature/web-sdk';\nimport { Dispatch, SetStateAction, useEffect, useState } from 'react';\nimport { useOpenFeatureClient } from './provider';\n\ntype ReactFlagEvaluationOptions = {\n /**\n * Suspend flag evaluations while the provider is not ready.\n * Set to false if you don't want to use React Suspense API.\n * Defaults to true.\n */\n suspend?: boolean,\n /**\n * Update the component if the provider emits a ConfigurationChanged event.\n * Set to false to prevent components from re-rendering when flag value changes\n * are received by the associated provider.\n * Defaults to true.\n */\n updateOnConfigurationChanged?: boolean,\n /**\n * Update the component when the OpenFeature context changes.\n * Set to false to prevent components from re-rendering when attributes which \n * may be factors in flag evaluation change.\n * Defaults to true.\n */\n updateOnContextChanged?: boolean,\n} & FlagEvaluationOptions;\n\nconst DEFAULT_OPTIONS: ReactFlagEvaluationOptions = {\n updateOnContextChanged: true,\n updateOnConfigurationChanged: true,\n suspend: true,\n};\n\nenum SuspendState {\n Pending,\n Success,\n Error\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * @param {string}flagKey the flag identifier\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @template T flag type\n * @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation\n */\nexport function useFeatureFlag<T extends FlagValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };\n const [, updateState] = useState<object | undefined>();\n const forceUpdate = () => {\n updateState({});\n };\n const client = useOpenFeatureClient();\n\n useEffect(() => {\n\n if (client.providerStatus !== ProviderStatus.READY) {\n // update when the provider is ready\n client.addHandler(ProviderEvents.Ready, forceUpdate);\n if (defaultedOptions.suspend) {\n suspend(client, updateState);\n }\n }\n\n if (defaultedOptions.updateOnContextChanged) {\n // update when the context changes\n client.addHandler(ProviderEvents.ContextChanged, forceUpdate);\n }\n\n if (defaultedOptions.updateOnConfigurationChanged) {\n // update when the provider configuration changes\n client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n }\n return () => {\n // cleanup the handlers (we can do this unconditionally with no impact)\n client.removeHandler(ProviderEvents.Ready, forceUpdate);\n client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);\n client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n };\n }, [client]);\n\n return getFlag(client, flagKey, defaultValue);\n}\n\nfunction getFlag<T extends FlagValue>(client: Client, flagKey: string, defaultValue: T): EvaluationDetails<T> {\n if (typeof defaultValue === 'boolean') {\n return client.getBooleanDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n } else if (typeof defaultValue === 'string') {\n return client.getStringDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n } else if (typeof defaultValue === 'number') {\n return client.getNumberDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n } else {\n return client.getObjectDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n }\n}\n\n/**\n * Suspend function. If this runs, components using the calling hook will be suspended.\n * @param {Client} client the OpenFeature client\n * @param {Function} updateState the state update function\n */\nfunction suspend(client: Client, updateState: Dispatch<SetStateAction<object | undefined>>) {\n let suspendResolver: () => void;\n let suspendRejecter: () => void;\n const suspendPromise = new Promise<void>((resolve) => {\n suspendResolver = () => {\n resolve();\n client.removeHandler(ProviderEvents.Ready, suspendResolver); // remove handler once it's run\n };\n suspendRejecter = () => {\n resolve(); // we still resolve here, since we don't want to throw errors\n client.removeHandler(ProviderEvents.Error, suspendRejecter); // remove handler once it's run\n };\n client.addHandler(ProviderEvents.Ready, suspendResolver);\n client.addHandler(ProviderEvents.Error, suspendRejecter);\n });\n updateState(suspenseWrapper(suspendPromise));\n}\n\n/**\n * Promise wrapper that throws unresolved promises to support React suspense.\n * @param {Promise<T>} promise to wrap\n * @template T flag type\n * @returns {Function} suspense-compliant lambda\n */\nfunction suspenseWrapper <T>(promise: Promise<T>) {\n let status: SuspendState = SuspendState.Pending;\n let result: T;\n\n const suspended = promise.then(\n (value) => {\n status = SuspendState.Success;\n result = value;\n },\n (error) => {\n status = SuspendState.Error;\n result = error;\n }\n );\n\n return () => {\n switch (status) {\n case SuspendState.Pending:\n throw suspended;\n case SuspendState.Success:\n return result;\n case SuspendState.Error:\n throw result;\n default:\n throw new Error('Suspending promise is in an unknown state.');\n }\n };\n};", "import * as React from 'react';\nimport { Client, OpenFeature } from '@openfeature/web-sdk';\n\ntype ClientOrClientName =\n | {\n /**\n * The name of the client.\n * @see OpenFeature.setProvider() and overloads.\n */\n clientName: string;\n /**\n * OpenFeature client to use.\n */\n client?: never;\n }\n | {\n /**\n * OpenFeature client to use.\n */\n client: Client;\n /**\n * The name of the client.\n * @see OpenFeature.setProvider() and overloads.\n */\n clientName?: never;\n };\n\ntype ProviderProps = {\n children?: React.ReactNode;\n} & ClientOrClientName;\n\nconst Context = React.createContext<Client | undefined>(undefined);\n\nexport const OpenFeatureProvider = ({ client, clientName, children }: ProviderProps) => {\n if (!client) {\n client = OpenFeature.getClient(clientName);\n }\n\n return <Context.Provider value={client}>{children}</Context.Provider>;\n};\n\nexport const useOpenFeatureClient = () => {\n const client = React.useContext(Context);\n\n if (!client) {\n throw new Error(\n 'No OpenFeature client available - components using OpenFeature must be wrapped with an <OpenFeatureProvider>'\n );\n }\n\n return client;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_web_sdk"]
|
|
7
7
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
|
|
1
18
|
// src/use-feature-flag.ts
|
|
2
|
-
import { ProviderEvents } from "@openfeature/web-sdk";
|
|
19
|
+
import { ProviderEvents, ProviderStatus } from "@openfeature/web-sdk";
|
|
3
20
|
import { useEffect, useState } from "react";
|
|
4
21
|
|
|
5
22
|
// src/provider.tsx
|
|
6
23
|
import * as React from "react";
|
|
7
24
|
import { OpenFeature } from "@openfeature/web-sdk";
|
|
8
25
|
var Context = React.createContext(void 0);
|
|
9
|
-
var OpenFeatureProvider = ({ client, children }) => {
|
|
26
|
+
var OpenFeatureProvider = ({ client, clientName, children }) => {
|
|
10
27
|
if (!client) {
|
|
11
|
-
client = OpenFeature.getClient();
|
|
28
|
+
client = OpenFeature.getClient(clientName);
|
|
12
29
|
}
|
|
13
30
|
return /* @__PURE__ */ React.createElement(Context.Provider, { value: client }, children);
|
|
14
31
|
};
|
|
@@ -23,15 +40,34 @@ var useOpenFeatureClient = () => {
|
|
|
23
40
|
};
|
|
24
41
|
|
|
25
42
|
// src/use-feature-flag.ts
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
var DEFAULT_OPTIONS = {
|
|
44
|
+
updateOnContextChanged: true,
|
|
45
|
+
updateOnConfigurationChanged: true,
|
|
46
|
+
suspend: true
|
|
47
|
+
};
|
|
48
|
+
function useFeatureFlag(flagKey, defaultValue, options) {
|
|
49
|
+
const defaultedOptions = __spreadValues(__spreadValues({}, DEFAULT_OPTIONS), options);
|
|
50
|
+
const [, updateState] = useState();
|
|
51
|
+
const forceUpdate = () => {
|
|
52
|
+
updateState({});
|
|
53
|
+
};
|
|
28
54
|
const client = useOpenFeatureClient();
|
|
29
55
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
if (client.providerStatus !== ProviderStatus.READY) {
|
|
57
|
+
client.addHandler(ProviderEvents.Ready, forceUpdate);
|
|
58
|
+
if (defaultedOptions.suspend) {
|
|
59
|
+
suspend(client, updateState);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (defaultedOptions.updateOnContextChanged) {
|
|
63
|
+
client.addHandler(ProviderEvents.ContextChanged, forceUpdate);
|
|
64
|
+
}
|
|
65
|
+
if (defaultedOptions.updateOnConfigurationChanged) {
|
|
66
|
+
client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);
|
|
67
|
+
}
|
|
33
68
|
return () => {
|
|
34
69
|
client.removeHandler(ProviderEvents.Ready, forceUpdate);
|
|
70
|
+
client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);
|
|
35
71
|
client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);
|
|
36
72
|
};
|
|
37
73
|
}, [client]);
|
|
@@ -48,6 +84,49 @@ function getFlag(client, flagKey, defaultValue) {
|
|
|
48
84
|
return client.getObjectDetails(flagKey, defaultValue);
|
|
49
85
|
}
|
|
50
86
|
}
|
|
87
|
+
function suspend(client, updateState) {
|
|
88
|
+
let suspendResolver;
|
|
89
|
+
let suspendRejecter;
|
|
90
|
+
const suspendPromise = new Promise((resolve) => {
|
|
91
|
+
suspendResolver = () => {
|
|
92
|
+
resolve();
|
|
93
|
+
client.removeHandler(ProviderEvents.Ready, suspendResolver);
|
|
94
|
+
};
|
|
95
|
+
suspendRejecter = () => {
|
|
96
|
+
resolve();
|
|
97
|
+
client.removeHandler(ProviderEvents.Error, suspendRejecter);
|
|
98
|
+
};
|
|
99
|
+
client.addHandler(ProviderEvents.Ready, suspendResolver);
|
|
100
|
+
client.addHandler(ProviderEvents.Error, suspendRejecter);
|
|
101
|
+
});
|
|
102
|
+
updateState(suspenseWrapper(suspendPromise));
|
|
103
|
+
}
|
|
104
|
+
function suspenseWrapper(promise) {
|
|
105
|
+
let status = 0 /* Pending */;
|
|
106
|
+
let result;
|
|
107
|
+
const suspended = promise.then(
|
|
108
|
+
(value) => {
|
|
109
|
+
status = 1 /* Success */;
|
|
110
|
+
result = value;
|
|
111
|
+
},
|
|
112
|
+
(error) => {
|
|
113
|
+
status = 2 /* Error */;
|
|
114
|
+
result = error;
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
return () => {
|
|
118
|
+
switch (status) {
|
|
119
|
+
case 0 /* Pending */:
|
|
120
|
+
throw suspended;
|
|
121
|
+
case 1 /* Success */:
|
|
122
|
+
return result;
|
|
123
|
+
case 2 /* Error */:
|
|
124
|
+
throw result;
|
|
125
|
+
default:
|
|
126
|
+
throw new Error("Suspending promise is in an unknown state.");
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
51
130
|
|
|
52
131
|
// src/index.ts
|
|
53
132
|
export * from "@openfeature/web-sdk";
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/use-feature-flag.ts", "../../src/provider.tsx", "../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { Client, EvaluationDetails, FlagValue, ProviderEvents } from '@openfeature/web-sdk';\nimport { useEffect, useState } from 'react';\nimport { useOpenFeatureClient } from './provider';\n\nexport function useFeatureFlag<T extends FlagValue>(flagKey: string, defaultValue: T): EvaluationDetails<T> {\n const [,
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Client, EvaluationDetails, FlagEvaluationOptions, FlagValue, ProviderEvents, ProviderStatus } from '@openfeature/web-sdk';\nimport { Dispatch, SetStateAction, useEffect, useState } from 'react';\nimport { useOpenFeatureClient } from './provider';\n\ntype ReactFlagEvaluationOptions = {\n /**\n * Suspend flag evaluations while the provider is not ready.\n * Set to false if you don't want to use React Suspense API.\n * Defaults to true.\n */\n suspend?: boolean,\n /**\n * Update the component if the provider emits a ConfigurationChanged event.\n * Set to false to prevent components from re-rendering when flag value changes\n * are received by the associated provider.\n * Defaults to true.\n */\n updateOnConfigurationChanged?: boolean,\n /**\n * Update the component when the OpenFeature context changes.\n * Set to false to prevent components from re-rendering when attributes which \n * may be factors in flag evaluation change.\n * Defaults to true.\n */\n updateOnContextChanged?: boolean,\n} & FlagEvaluationOptions;\n\nconst DEFAULT_OPTIONS: ReactFlagEvaluationOptions = {\n updateOnContextChanged: true,\n updateOnConfigurationChanged: true,\n suspend: true,\n};\n\nenum SuspendState {\n Pending,\n Success,\n Error\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * @param {string}flagKey the flag identifier\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @template T flag type\n * @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation\n */\nexport function useFeatureFlag<T extends FlagValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };\n const [, updateState] = useState<object | undefined>();\n const forceUpdate = () => {\n updateState({});\n };\n const client = useOpenFeatureClient();\n\n useEffect(() => {\n\n if (client.providerStatus !== ProviderStatus.READY) {\n // update when the provider is ready\n client.addHandler(ProviderEvents.Ready, forceUpdate);\n if (defaultedOptions.suspend) {\n suspend(client, updateState);\n }\n }\n\n if (defaultedOptions.updateOnContextChanged) {\n // update when the context changes\n client.addHandler(ProviderEvents.ContextChanged, forceUpdate);\n }\n\n if (defaultedOptions.updateOnConfigurationChanged) {\n // update when the provider configuration changes\n client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n }\n return () => {\n // cleanup the handlers (we can do this unconditionally with no impact)\n client.removeHandler(ProviderEvents.Ready, forceUpdate);\n client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);\n client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n };\n }, [client]);\n\n return getFlag(client, flagKey, defaultValue);\n}\n\nfunction getFlag<T extends FlagValue>(client: Client, flagKey: string, defaultValue: T): EvaluationDetails<T> {\n if (typeof defaultValue === 'boolean') {\n return client.getBooleanDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n } else if (typeof defaultValue === 'string') {\n return client.getStringDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n } else if (typeof defaultValue === 'number') {\n return client.getNumberDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n } else {\n return client.getObjectDetails(flagKey, defaultValue) as EvaluationDetails<T>;\n }\n}\n\n/**\n * Suspend function. If this runs, components using the calling hook will be suspended.\n * @param {Client} client the OpenFeature client\n * @param {Function} updateState the state update function\n */\nfunction suspend(client: Client, updateState: Dispatch<SetStateAction<object | undefined>>) {\n let suspendResolver: () => void;\n let suspendRejecter: () => void;\n const suspendPromise = new Promise<void>((resolve) => {\n suspendResolver = () => {\n resolve();\n client.removeHandler(ProviderEvents.Ready, suspendResolver); // remove handler once it's run\n };\n suspendRejecter = () => {\n resolve(); // we still resolve here, since we don't want to throw errors\n client.removeHandler(ProviderEvents.Error, suspendRejecter); // remove handler once it's run\n };\n client.addHandler(ProviderEvents.Ready, suspendResolver);\n client.addHandler(ProviderEvents.Error, suspendRejecter);\n });\n updateState(suspenseWrapper(suspendPromise));\n}\n\n/**\n * Promise wrapper that throws unresolved promises to support React suspense.\n * @param {Promise<T>} promise to wrap\n * @template T flag type\n * @returns {Function} suspense-compliant lambda\n */\nfunction suspenseWrapper <T>(promise: Promise<T>) {\n let status: SuspendState = SuspendState.Pending;\n let result: T;\n\n const suspended = promise.then(\n (value) => {\n status = SuspendState.Success;\n result = value;\n },\n (error) => {\n status = SuspendState.Error;\n result = error;\n }\n );\n\n return () => {\n switch (status) {\n case SuspendState.Pending:\n throw suspended;\n case SuspendState.Success:\n return result;\n case SuspendState.Error:\n throw result;\n default:\n throw new Error('Suspending promise is in an unknown state.');\n }\n };\n};", "import * as React from 'react';\nimport { Client, OpenFeature } from '@openfeature/web-sdk';\n\ntype ClientOrClientName =\n | {\n /**\n * The name of the client.\n * @see OpenFeature.setProvider() and overloads.\n */\n clientName: string;\n /**\n * OpenFeature client to use.\n */\n client?: never;\n }\n | {\n /**\n * OpenFeature client to use.\n */\n client: Client;\n /**\n * The name of the client.\n * @see OpenFeature.setProvider() and overloads.\n */\n clientName?: never;\n };\n\ntype ProviderProps = {\n children?: React.ReactNode;\n} & ClientOrClientName;\n\nconst Context = React.createContext<Client | undefined>(undefined);\n\nexport const OpenFeatureProvider = ({ client, clientName, children }: ProviderProps) => {\n if (!client) {\n client = OpenFeature.getClient(clientName);\n }\n\n return <Context.Provider value={client}>{children}</Context.Provider>;\n};\n\nexport const useOpenFeatureClient = () => {\n const client = React.useContext(Context);\n\n if (!client) {\n throw new Error(\n 'No OpenFeature client available - components using OpenFeature must be wrapped with an <OpenFeatureProvider>'\n );\n }\n\n return client;\n};\n", "export * from './use-feature-flag';\nexport * from './provider';\n// re-export the web-sdk so consumers can access that API from the react-sdk\nexport * from '@openfeature/web-sdk';\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|