@openfeature/react-sdk 0.2.3-experimental → 0.2.4-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 +23 -10
- package/dist/cjs/index.js +89 -26
- package/dist/cjs/index.js.map +3 -3
- package/dist/esm/index.js +94 -27
- package/dist/esm/index.js.map +3 -3
- package/dist/types.d.ts +77 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.7.0&color=yellow&style=for-the-badge" />
|
|
17
17
|
</a>
|
|
18
18
|
<!-- x-release-please-start-version -->
|
|
19
|
-
<a href="https://github.com/open-feature/js-sdk/releases/tag/react-sdk-v0.2.
|
|
20
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.2.
|
|
19
|
+
<a href="https://github.com/open-feature/js-sdk/releases/tag/react-sdk-v0.2.4-experimental">
|
|
20
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.2.4-experimental&color=blue&style=for-the-badge" />
|
|
21
21
|
</a>
|
|
22
22
|
<!-- x-release-please-end -->
|
|
23
23
|
<br/>
|
|
@@ -87,10 +87,12 @@ The following list contains the peer dependencies of `@openfeature/react-sdk` wi
|
|
|
87
87
|
|
|
88
88
|
### Usage
|
|
89
89
|
|
|
90
|
+
The `OpenFeatureProvider` represents a scope for feature flag evaluations within a React application.
|
|
91
|
+
It binds an OpenFeature client to all evaluations within child components, and allows the use of evaluation hooks.
|
|
90
92
|
The example below shows how to use the `OpenFeatureProvider` with OpenFeature's `InMemoryProvider`.
|
|
91
93
|
|
|
92
94
|
```tsx
|
|
93
|
-
import { EvaluationContext, OpenFeatureProvider,
|
|
95
|
+
import { EvaluationContext, OpenFeatureProvider, useFlag, OpenFeature, InMemoryProvider } from '@openfeature/react-sdk';
|
|
94
96
|
|
|
95
97
|
const flagConfig = {
|
|
96
98
|
'new-message': {
|
|
@@ -109,8 +111,11 @@ const flagConfig = {
|
|
|
109
111
|
},
|
|
110
112
|
};
|
|
111
113
|
|
|
114
|
+
// Instantiate and set our provider (be sure this only happens once)!
|
|
115
|
+
// Note: there's no need to await its initialization, the React SDK handles re-rendering and suspense for you!
|
|
112
116
|
OpenFeature.setProvider(new InMemoryProvider(flagConfig));
|
|
113
117
|
|
|
118
|
+
// Enclose your content in the configured provider
|
|
114
119
|
function App() {
|
|
115
120
|
return (
|
|
116
121
|
<OpenFeatureProvider>
|
|
@@ -120,11 +125,12 @@ function App() {
|
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
function Page() {
|
|
123
|
-
|
|
128
|
+
// Use the "query-style" flag evaluation hook.
|
|
129
|
+
const { value: showNewMessage } = useFlag('new-message', true);
|
|
124
130
|
return (
|
|
125
131
|
<div className="App">
|
|
126
132
|
<header className="App-header">
|
|
127
|
-
{
|
|
133
|
+
{showNewMessage ? <p>Welcome to this OpenFeature-enabled React app!</p> : <p>Welcome to this React app.</p>}
|
|
128
134
|
</header>
|
|
129
135
|
</div>
|
|
130
136
|
)
|
|
@@ -132,12 +138,19 @@ function Page() {
|
|
|
132
138
|
|
|
133
139
|
export default App;
|
|
134
140
|
```
|
|
141
|
+
You can use the strongly-typed flag value and flag evaluation detail hooks as well, if you prefer.
|
|
135
142
|
|
|
136
|
-
|
|
143
|
+
```tsx
|
|
144
|
+
import { useBooleanFlagValue } from '@openfeature/react-sdk';
|
|
145
|
+
|
|
146
|
+
// boolean flag evaluation
|
|
147
|
+
const value = useBooleanFlagValue('new-message', false);
|
|
148
|
+
```
|
|
137
149
|
|
|
138
150
|
```tsx
|
|
139
151
|
import { useBooleanFlagDetails } from '@openfeature/react-sdk';
|
|
140
152
|
|
|
153
|
+
// "detailed" boolean flag evaluation
|
|
141
154
|
const {
|
|
142
155
|
value,
|
|
143
156
|
variant,
|
|
@@ -178,7 +191,7 @@ You can disable this feature in the hook options:
|
|
|
178
191
|
|
|
179
192
|
```tsx
|
|
180
193
|
function Page() {
|
|
181
|
-
const
|
|
194
|
+
const showNewMessage = useBooleanFlagValue('new-message', false, { updateOnContextChanged: false });
|
|
182
195
|
return (
|
|
183
196
|
<MyComponents></MyComponents>
|
|
184
197
|
)
|
|
@@ -195,7 +208,7 @@ You can disable this feature in the hook options:
|
|
|
195
208
|
|
|
196
209
|
```tsx
|
|
197
210
|
function Page() {
|
|
198
|
-
const
|
|
211
|
+
const showNewMessage = useBooleanFlagValue('new-message', false, { updateOnConfigurationChanged: false });
|
|
199
212
|
return (
|
|
200
213
|
<MyComponents></MyComponents>
|
|
201
214
|
)
|
|
@@ -222,11 +235,11 @@ function Content() {
|
|
|
222
235
|
|
|
223
236
|
function Message() {
|
|
224
237
|
// component to render after READY.
|
|
225
|
-
const
|
|
238
|
+
const showNewMessage = useBooleanFlagValue('new-message', false);
|
|
226
239
|
|
|
227
240
|
return (
|
|
228
241
|
<>
|
|
229
|
-
{
|
|
242
|
+
{showNewMessage ? (
|
|
230
243
|
<p>Welcome to this OpenFeature-enabled React app!</p>
|
|
231
244
|
) : (
|
|
232
245
|
<p>Welcome to this plain old React app!</p>
|
package/dist/cjs/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __export(src_exports, {
|
|
|
34
34
|
OpenFeatureProvider: () => OpenFeatureProvider,
|
|
35
35
|
useBooleanFlagDetails: () => useBooleanFlagDetails,
|
|
36
36
|
useBooleanFlagValue: () => useBooleanFlagValue,
|
|
37
|
+
useFlag: () => useFlag,
|
|
37
38
|
useNumberFlagDetails: () => useNumberFlagDetails,
|
|
38
39
|
useNumberFlagValue: () => useNumberFlagValue,
|
|
39
40
|
useObjectFlagDetails: () => useObjectFlagDetails,
|
|
@@ -44,11 +45,11 @@ __export(src_exports, {
|
|
|
44
45
|
});
|
|
45
46
|
module.exports = __toCommonJS(src_exports);
|
|
46
47
|
|
|
47
|
-
// src/use-feature-flag.ts
|
|
48
|
+
// src/evaluation/use-feature-flag.ts
|
|
48
49
|
var import_web_sdk2 = require("@openfeature/web-sdk");
|
|
49
50
|
var import_react = require("react");
|
|
50
51
|
|
|
51
|
-
// src/provider.tsx
|
|
52
|
+
// src/provider/provider.tsx
|
|
52
53
|
var React = __toESM(require("react"));
|
|
53
54
|
var import_web_sdk = require("@openfeature/web-sdk");
|
|
54
55
|
var Context = React.createContext(void 0);
|
|
@@ -68,44 +69,68 @@ var useOpenFeatureClient = () => {
|
|
|
68
69
|
return client;
|
|
69
70
|
};
|
|
70
71
|
|
|
71
|
-
// src/use-feature-flag.ts
|
|
72
|
+
// src/evaluation/use-feature-flag.ts
|
|
72
73
|
var DEFAULT_OPTIONS = {
|
|
73
74
|
updateOnContextChanged: true,
|
|
74
75
|
updateOnConfigurationChanged: true,
|
|
75
76
|
suspendUntilReady: true,
|
|
76
77
|
suspendWhileReconciling: false
|
|
77
78
|
};
|
|
79
|
+
function useFlag(flagKey, defaultValue, options) {
|
|
80
|
+
const query = typeof defaultValue === "boolean" ? new HookFlagQuery(useBooleanFlagDetails(flagKey, defaultValue, options)) : typeof defaultValue === "number" ? new HookFlagQuery(useNumberFlagDetails(flagKey, defaultValue, options)) : typeof defaultValue === "string" ? new HookFlagQuery(useStringFlagDetails(flagKey, defaultValue, options)) : new HookFlagQuery(useObjectFlagDetails(flagKey, defaultValue, options));
|
|
81
|
+
return query;
|
|
82
|
+
}
|
|
78
83
|
function useBooleanFlagValue(flagKey, defaultValue, options) {
|
|
79
84
|
return useBooleanFlagDetails(flagKey, defaultValue, options).value;
|
|
80
85
|
}
|
|
81
86
|
function useBooleanFlagDetails(flagKey, defaultValue, options) {
|
|
82
|
-
return attachHandlersAndResolve(
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
return attachHandlersAndResolve(
|
|
88
|
+
flagKey,
|
|
89
|
+
defaultValue,
|
|
90
|
+
(client) => {
|
|
91
|
+
return client.getBooleanDetails;
|
|
92
|
+
},
|
|
93
|
+
options
|
|
94
|
+
);
|
|
85
95
|
}
|
|
86
96
|
function useStringFlagValue(flagKey, defaultValue, options) {
|
|
87
97
|
return useStringFlagDetails(flagKey, defaultValue, options).value;
|
|
88
98
|
}
|
|
89
99
|
function useStringFlagDetails(flagKey, defaultValue, options) {
|
|
90
|
-
return attachHandlersAndResolve(
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
return attachHandlersAndResolve(
|
|
101
|
+
flagKey,
|
|
102
|
+
defaultValue,
|
|
103
|
+
(client) => {
|
|
104
|
+
return client.getStringDetails;
|
|
105
|
+
},
|
|
106
|
+
options
|
|
107
|
+
);
|
|
93
108
|
}
|
|
94
109
|
function useNumberFlagValue(flagKey, defaultValue, options) {
|
|
95
110
|
return useNumberFlagDetails(flagKey, defaultValue, options).value;
|
|
96
111
|
}
|
|
97
112
|
function useNumberFlagDetails(flagKey, defaultValue, options) {
|
|
98
|
-
return attachHandlersAndResolve(
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
return attachHandlersAndResolve(
|
|
114
|
+
flagKey,
|
|
115
|
+
defaultValue,
|
|
116
|
+
(client) => {
|
|
117
|
+
return client.getNumberDetails;
|
|
118
|
+
},
|
|
119
|
+
options
|
|
120
|
+
);
|
|
101
121
|
}
|
|
102
122
|
function useObjectFlagValue(flagKey, defaultValue, options) {
|
|
103
123
|
return useObjectFlagDetails(flagKey, defaultValue, options).value;
|
|
104
124
|
}
|
|
105
125
|
function useObjectFlagDetails(flagKey, defaultValue, options) {
|
|
106
|
-
return attachHandlersAndResolve(
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
return attachHandlersAndResolve(
|
|
127
|
+
flagKey,
|
|
128
|
+
defaultValue,
|
|
129
|
+
(client) => {
|
|
130
|
+
return client.getObjectDetails;
|
|
131
|
+
},
|
|
132
|
+
options
|
|
133
|
+
);
|
|
109
134
|
}
|
|
110
135
|
function attachHandlersAndResolve(flagKey, defaultValue, resolver, options) {
|
|
111
136
|
const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -115,7 +140,13 @@ function attachHandlersAndResolve(flagKey, defaultValue, resolver, options) {
|
|
|
115
140
|
updateState({});
|
|
116
141
|
};
|
|
117
142
|
const suspendRef = () => {
|
|
118
|
-
suspend(
|
|
143
|
+
suspend(
|
|
144
|
+
client,
|
|
145
|
+
updateState,
|
|
146
|
+
import_web_sdk2.ProviderEvents.ContextChanged,
|
|
147
|
+
import_web_sdk2.ProviderEvents.ConfigurationChanged,
|
|
148
|
+
import_web_sdk2.ProviderEvents.Ready
|
|
149
|
+
);
|
|
119
150
|
};
|
|
120
151
|
(0, import_react.useEffect)(() => {
|
|
121
152
|
if (client.providerStatus === import_web_sdk2.ProviderStatus.NOT_READY) {
|
|
@@ -166,16 +197,13 @@ function suspend(client, updateState, ...resumeEvents) {
|
|
|
166
197
|
function suspenseWrapper(promise) {
|
|
167
198
|
let status = 0 /* Pending */;
|
|
168
199
|
let result;
|
|
169
|
-
const suspended = promise.then(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
result = error;
|
|
177
|
-
}
|
|
178
|
-
);
|
|
200
|
+
const suspended = promise.then((value) => {
|
|
201
|
+
status = 1 /* Success */;
|
|
202
|
+
result = value;
|
|
203
|
+
}).catch((error) => {
|
|
204
|
+
status = 2 /* Error */;
|
|
205
|
+
result = error;
|
|
206
|
+
});
|
|
179
207
|
return () => {
|
|
180
208
|
switch (status) {
|
|
181
209
|
case 0 /* Pending */:
|
|
@@ -189,6 +217,41 @@ function suspenseWrapper(promise) {
|
|
|
189
217
|
}
|
|
190
218
|
};
|
|
191
219
|
}
|
|
220
|
+
var HookFlagQuery = class {
|
|
221
|
+
constructor(_details) {
|
|
222
|
+
this._details = _details;
|
|
223
|
+
}
|
|
224
|
+
get details() {
|
|
225
|
+
return this._details;
|
|
226
|
+
}
|
|
227
|
+
get value() {
|
|
228
|
+
return this._details?.value;
|
|
229
|
+
}
|
|
230
|
+
get variant() {
|
|
231
|
+
return this._details.variant;
|
|
232
|
+
}
|
|
233
|
+
get flagMetadata() {
|
|
234
|
+
return this._details.flagMetadata;
|
|
235
|
+
}
|
|
236
|
+
get reason() {
|
|
237
|
+
return this._details.reason;
|
|
238
|
+
}
|
|
239
|
+
get isError() {
|
|
240
|
+
return !!this._details?.errorCode || this._details.reason == import_web_sdk2.StandardResolutionReasons.ERROR;
|
|
241
|
+
}
|
|
242
|
+
get errorCode() {
|
|
243
|
+
return this._details?.errorCode;
|
|
244
|
+
}
|
|
245
|
+
get errorMessage() {
|
|
246
|
+
return this._details?.errorMessage;
|
|
247
|
+
}
|
|
248
|
+
get isAuthoritative() {
|
|
249
|
+
return !this.isError && this._details.reason != import_web_sdk2.StandardResolutionReasons.STALE && this._details.reason != import_web_sdk2.StandardResolutionReasons.DISABLED;
|
|
250
|
+
}
|
|
251
|
+
get type() {
|
|
252
|
+
return typeof this._details.value;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
192
255
|
|
|
193
256
|
// src/index.ts
|
|
194
257
|
__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
|
-
"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, FlagEvaluationOptions, FlagValue, JsonValue, 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 show suspense fallbacks until the provider is initialized.\n * Defaults to true.\n */\n suspendUntilReady?: boolean,\n /**\n * Suspend flag evaluations while the provider's context is being reconciled.\n * Set to true if you want to show suspense fallbacks while flags are re-evaluated after context changes.\n * Defaults to false.\n */\n suspendWhileReconciling?: 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 suspendUntilReady: true,\n suspendWhileReconciling: false,\n};\n\nenum SuspendState {\n Pending,\n Success,\n Error\n}\n\n/**\n * Evaluates a feature flag, returning a boolean.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagValue(flagKey: string, defaultValue: boolean, options?: ReactFlagEvaluationOptions): boolean {\n return useBooleanFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<boolean>} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagDetails(flagKey: string, defaultValue: boolean, options?: ReactFlagEvaluationOptions): EvaluationDetails<boolean> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getBooleanDetails;\n }, options);\n}\n\n/**\n * Evaluates a feature flag, returning a string.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagValue<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): T {\n return useStringFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<string>} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagDetails<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getStringDetails<T>;\n }, options);\n}\n\n/**\n * Evaluates a feature flag, returning a number.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagValue<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): T {\n return useNumberFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<number>} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagDetails<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getNumberDetails<T>;\n }, options);\n}\n\n/**\n * Evaluates a feature flag, returning an object.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagValue<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): T {\n return useObjectFlagDetails<T>(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @param {T} defaultValue the default value\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagDetails<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getObjectDetails<T>;\n }, options);\n}\n\nfunction attachHandlersAndResolve<T extends FlagValue>(flagKey: string, defaultValue: T, resolver: (client: Client) => (flagKey: string, defaultValue: T) => EvaluationDetails<T>, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };\n const [, updateState] = useState<object | undefined>();\n const client = useOpenFeatureClient();\n const forceUpdate = () => {\n updateState({});\n };\n const suspendRef = () => {\n suspend(client, updateState, ProviderEvents.ContextChanged, ProviderEvents.ConfigurationChanged, ProviderEvents.Ready);\n };\n\n useEffect(() => {\n if (client.providerStatus === ProviderStatus.NOT_READY) {\n // update when the provider is ready\n client.addHandler(ProviderEvents.Ready, forceUpdate);\n if (defaultedOptions.suspendUntilReady) {\n suspend(client, updateState, ProviderEvents.Ready);\n }\n }\n\n if (defaultedOptions.updateOnContextChanged) {\n // update when the context changes\n client.addHandler(ProviderEvents.ContextChanged, forceUpdate);\n if (defaultedOptions.suspendWhileReconciling) {\n client.addHandler(ProviderEvents.Reconciling, suspendRef);\n }\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.Ready, forceUpdate);\n client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);\n client.removeHandler(ProviderEvents.Reconciling, suspendRef);\n };\n }, []);\n \n useEffect(() => {\n if (defaultedOptions.updateOnConfigurationChanged) {\n // update when the provider configuration changes\n client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n };\n }, []);\n\n return resolver(client).call(client, flagKey, defaultValue);\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 * @param {ProviderEvents[]} resumeEvents list of events which will resume the suspend\n */\nfunction suspend(client: Client, updateState: Dispatch<SetStateAction<object | undefined>>, ...resumeEvents: ProviderEvents[]) {\n\n let suspendResolver: () => void;\n \n const suspendPromise = new Promise<void>((resolve) => {\n suspendResolver = () => {\n resolve();\n resumeEvents.forEach((e) => {\n client.removeHandler(e, suspendResolver); // remove handlers once they've run\n });\n client.removeHandler(ProviderEvents.Error, suspendResolver);\n };\n resumeEvents.forEach((e) => {\n client.addHandler(e, suspendResolver);\n });\n client.addHandler(ProviderEvents.Error, suspendResolver); // we never want to throw, resolve with errors - we may make this configurable later\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 ClientOrDomain =\n | {\n /**\n * An identifier which logically binds clients with providers\n * @see OpenFeature.setProvider() and overloads.\n */\n domain?: string;\n client?: never;\n }\n | {\n /**\n * OpenFeature client to use.\n */\n client?: Client;\n domain?: never;\n };\n\ntype ProviderProps = {\n children?: React.ReactNode;\n} & ClientOrDomain;\n\nconst Context = React.createContext<Client | undefined>(undefined);\n\nexport const OpenFeatureProvider = ({ client, domain, children }: ProviderProps) => {\n if (!client) {\n client = OpenFeature.getClient(domain);\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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,
|
|
3
|
+
"sources": ["../../src/index.ts", "../../src/evaluation/use-feature-flag.ts", "../../src/provider/provider.tsx"],
|
|
4
|
+
"sourcesContent": ["export * from './evaluation';\nexport * from './query';\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 {\n Client,\n EvaluationDetails,\n FlagEvaluationOptions,\n FlagValue,\n JsonValue,\n ProviderEvents,\n ProviderStatus,\n StandardResolutionReasons,\n} from '@openfeature/web-sdk';\nimport { Dispatch, SetStateAction, useEffect, useState } from 'react';\nimport { useOpenFeatureClient } from '../provider';\nimport { FlagQuery } from '../query';\n\ntype ReactFlagEvaluationOptions = {\n /**\n * Suspend flag evaluations while the provider is not ready.\n * Set to false if you don't want to show suspense fallbacks until the provider is initialized.\n * Defaults to true.\n */\n suspendUntilReady?: boolean;\n /**\n * Suspend flag evaluations while the provider's context is being reconciled.\n * Set to true if you want to show suspense fallbacks while flags are re-evaluated after context changes.\n * Defaults to false.\n */\n suspendWhileReconciling?: 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 suspendUntilReady: true,\n suspendWhileReconciling: false,\n};\n\nenum SuspendState {\n Pending,\n Success,\n Error,\n}\n\n// This type is a bit wild-looking, but I think we need it.\n// We have to use the conditional, because otherwise useFlag('key', false) would return false, not boolean (too constrained).\n// We have a duplicate for the hook return below, this one is just used for casting because the name isn't as clear\ntype ConstrainedFlagQuery<T> = FlagQuery<\n T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T extends JsonValue\n ? T\n : JsonValue\n>;\n\n/**\n * Evaluates a feature flag generically, returning an react-flavored queryable object.\n * The resolver method to use is based on the type of the defaultValue.\n * For type-specific hooks, use {@link useBooleanFlagValue}, {@link useBooleanFlagDetails} and equivalents.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {FlagValue} T A optional generic argument constraining the default.\n * @param {T} defaultValue the default value; used to determine what resolved type should be used.\n * @param {ReactFlagEvaluationOptions} options for this evaluation\n * @returns { FlagQuery } a queryable object containing useful information about the flag.\n */\nexport function useFlag<T extends FlagValue = FlagValue>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): FlagQuery<\nT extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T extends JsonValue\n ? T\n : JsonValue\n> {\n // use the default value to determine the resolver to call\n const query =\n typeof defaultValue === 'boolean'\n ? new HookFlagQuery<boolean>(useBooleanFlagDetails(flagKey, defaultValue, options))\n : typeof defaultValue === 'number'\n ? new HookFlagQuery<number>(useNumberFlagDetails(flagKey, defaultValue, options))\n : typeof defaultValue === 'string'\n ? new HookFlagQuery<string>(useStringFlagDetails(flagKey, defaultValue, options))\n : new HookFlagQuery<JsonValue>(useObjectFlagDetails(flagKey, defaultValue, options));\n // TS sees this as HookFlagQuery<JsonValue>, because the compiler isn't aware of the `typeof` checks above.\n return query as unknown as ConstrainedFlagQuery<T>;\n}\n\n/**\n * Evaluates a feature flag, returning a boolean.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagValue(\n flagKey: string,\n defaultValue: boolean,\n options?: ReactFlagEvaluationOptions,\n): boolean {\n return useBooleanFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<boolean>} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagDetails(\n flagKey: string,\n defaultValue: boolean,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<boolean> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getBooleanDetails;\n },\n options,\n );\n}\n\n/**\n * Evaluates a feature flag, returning a string.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagValue<T extends string = string>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): string {\n return useStringFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<string>} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagDetails<T extends string = string>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<string> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getStringDetails<T>;\n },\n options,\n );\n}\n\n/**\n * Evaluates a feature flag, returning a number.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagValue<T extends number = number>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): number {\n return useNumberFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<number>} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagDetails<T extends number = number>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<number> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getNumberDetails<T>;\n },\n options,\n );\n}\n\n/**\n * Evaluates a feature flag, returning an object.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagValue<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): T {\n return useObjectFlagDetails<T>(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @param {T} defaultValue the default value\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagDetails<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<T> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getObjectDetails<T>;\n },\n options,\n );\n}\n\nfunction attachHandlersAndResolve<T extends FlagValue>(\n flagKey: string,\n defaultValue: T,\n resolver: (client: Client) => (flagKey: string, defaultValue: T) => EvaluationDetails<T>,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<T> {\n const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };\n const [, updateState] = useState<object | undefined>();\n const client = useOpenFeatureClient();\n const forceUpdate = () => {\n updateState({});\n };\n const suspendRef = () => {\n suspend(\n client,\n updateState,\n ProviderEvents.ContextChanged,\n ProviderEvents.ConfigurationChanged,\n ProviderEvents.Ready,\n );\n };\n\n useEffect(() => {\n if (client.providerStatus === ProviderStatus.NOT_READY) {\n // update when the provider is ready\n client.addHandler(ProviderEvents.Ready, forceUpdate);\n if (defaultedOptions.suspendUntilReady) {\n suspend(client, updateState, ProviderEvents.Ready);\n }\n }\n\n if (defaultedOptions.updateOnContextChanged) {\n // update when the context changes\n client.addHandler(ProviderEvents.ContextChanged, forceUpdate);\n if (defaultedOptions.suspendWhileReconciling) {\n client.addHandler(ProviderEvents.Reconciling, suspendRef);\n }\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.Ready, forceUpdate);\n client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);\n client.removeHandler(ProviderEvents.Reconciling, suspendRef);\n };\n }, []);\n\n useEffect(() => {\n if (defaultedOptions.updateOnConfigurationChanged) {\n // update when the provider configuration changes\n client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n };\n }, []);\n\n return resolver(client).call(client, flagKey, defaultValue);\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 * @param {ProviderEvents[]} resumeEvents list of events which will resume the suspend\n */\nfunction suspend(\n client: Client,\n updateState: Dispatch<SetStateAction<object | undefined>>,\n ...resumeEvents: ProviderEvents[]\n) {\n let suspendResolver: () => void;\n\n const suspendPromise = new Promise<void>((resolve) => {\n suspendResolver = () => {\n resolve();\n resumeEvents.forEach((e) => {\n client.removeHandler(e, suspendResolver); // remove handlers once they've run\n });\n client.removeHandler(ProviderEvents.Error, suspendResolver);\n };\n resumeEvents.forEach((e) => {\n client.addHandler(e, suspendResolver);\n });\n client.addHandler(ProviderEvents.Error, suspendResolver); // we never want to throw, resolve with errors - we may make this configurable later\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\n .then((value) => {\n status = SuspendState.Success;\n result = value;\n })\n .catch((error) => {\n status = SuspendState.Error;\n result = error;\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}\n\n// FlagQuery implementation, do not export\nclass HookFlagQuery<T extends FlagValue = FlagValue> implements FlagQuery {\n constructor(private _details: EvaluationDetails<T>) {}\n\n get details() {\n return this._details;\n }\n\n get value() {\n return this._details?.value;\n }\n\n get variant() {\n return this._details.variant;\n }\n\n get flagMetadata() {\n return this._details.flagMetadata;\n }\n\n get reason() {\n return this._details.reason;\n }\n\n get isError() {\n return !!this._details?.errorCode || this._details.reason == StandardResolutionReasons.ERROR;\n }\n\n get errorCode() {\n return this._details?.errorCode;\n }\n\n get errorMessage() {\n return this._details?.errorMessage;\n }\n\n get isAuthoritative() {\n return (\n !this.isError &&\n this._details.reason != StandardResolutionReasons.STALE &&\n this._details.reason != StandardResolutionReasons.DISABLED\n );\n }\n\n get type() {\n return typeof this._details.value;\n }\n}\n", "import * as React from 'react';\nimport { Client, OpenFeature } from '@openfeature/web-sdk';\n\ntype ClientOrDomain =\n | {\n /**\n * An identifier which logically binds clients with providers\n * @see OpenFeature.setProvider() and overloads.\n */\n domain?: string;\n client?: never;\n }\n | {\n /**\n * OpenFeature client to use.\n */\n client?: Client;\n domain?: never;\n };\n\ntype ProviderProps = {\n children?: React.ReactNode;\n} & ClientOrDomain;\n\nconst Context = React.createContext<Client | undefined>(undefined);\n\nexport const OpenFeatureProvider = ({ client, domain, children }: ProviderProps) => {\n if (!client) {\n client = OpenFeature.getClient(domain);\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,8 +1,12 @@
|
|
|
1
|
-
// src/use-feature-flag.ts
|
|
2
|
-
import {
|
|
1
|
+
// src/evaluation/use-feature-flag.ts
|
|
2
|
+
import {
|
|
3
|
+
ProviderEvents,
|
|
4
|
+
ProviderStatus,
|
|
5
|
+
StandardResolutionReasons
|
|
6
|
+
} from "@openfeature/web-sdk";
|
|
3
7
|
import { useEffect, useState } from "react";
|
|
4
8
|
|
|
5
|
-
// src/provider.tsx
|
|
9
|
+
// src/provider/provider.tsx
|
|
6
10
|
import * as React from "react";
|
|
7
11
|
import { OpenFeature } from "@openfeature/web-sdk";
|
|
8
12
|
var Context = React.createContext(void 0);
|
|
@@ -22,44 +26,68 @@ var useOpenFeatureClient = () => {
|
|
|
22
26
|
return client;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
|
-
// src/use-feature-flag.ts
|
|
29
|
+
// src/evaluation/use-feature-flag.ts
|
|
26
30
|
var DEFAULT_OPTIONS = {
|
|
27
31
|
updateOnContextChanged: true,
|
|
28
32
|
updateOnConfigurationChanged: true,
|
|
29
33
|
suspendUntilReady: true,
|
|
30
34
|
suspendWhileReconciling: false
|
|
31
35
|
};
|
|
36
|
+
function useFlag(flagKey, defaultValue, options) {
|
|
37
|
+
const query = typeof defaultValue === "boolean" ? new HookFlagQuery(useBooleanFlagDetails(flagKey, defaultValue, options)) : typeof defaultValue === "number" ? new HookFlagQuery(useNumberFlagDetails(flagKey, defaultValue, options)) : typeof defaultValue === "string" ? new HookFlagQuery(useStringFlagDetails(flagKey, defaultValue, options)) : new HookFlagQuery(useObjectFlagDetails(flagKey, defaultValue, options));
|
|
38
|
+
return query;
|
|
39
|
+
}
|
|
32
40
|
function useBooleanFlagValue(flagKey, defaultValue, options) {
|
|
33
41
|
return useBooleanFlagDetails(flagKey, defaultValue, options).value;
|
|
34
42
|
}
|
|
35
43
|
function useBooleanFlagDetails(flagKey, defaultValue, options) {
|
|
36
|
-
return attachHandlersAndResolve(
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
return attachHandlersAndResolve(
|
|
45
|
+
flagKey,
|
|
46
|
+
defaultValue,
|
|
47
|
+
(client) => {
|
|
48
|
+
return client.getBooleanDetails;
|
|
49
|
+
},
|
|
50
|
+
options
|
|
51
|
+
);
|
|
39
52
|
}
|
|
40
53
|
function useStringFlagValue(flagKey, defaultValue, options) {
|
|
41
54
|
return useStringFlagDetails(flagKey, defaultValue, options).value;
|
|
42
55
|
}
|
|
43
56
|
function useStringFlagDetails(flagKey, defaultValue, options) {
|
|
44
|
-
return attachHandlersAndResolve(
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
return attachHandlersAndResolve(
|
|
58
|
+
flagKey,
|
|
59
|
+
defaultValue,
|
|
60
|
+
(client) => {
|
|
61
|
+
return client.getStringDetails;
|
|
62
|
+
},
|
|
63
|
+
options
|
|
64
|
+
);
|
|
47
65
|
}
|
|
48
66
|
function useNumberFlagValue(flagKey, defaultValue, options) {
|
|
49
67
|
return useNumberFlagDetails(flagKey, defaultValue, options).value;
|
|
50
68
|
}
|
|
51
69
|
function useNumberFlagDetails(flagKey, defaultValue, options) {
|
|
52
|
-
return attachHandlersAndResolve(
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
return attachHandlersAndResolve(
|
|
71
|
+
flagKey,
|
|
72
|
+
defaultValue,
|
|
73
|
+
(client) => {
|
|
74
|
+
return client.getNumberDetails;
|
|
75
|
+
},
|
|
76
|
+
options
|
|
77
|
+
);
|
|
55
78
|
}
|
|
56
79
|
function useObjectFlagValue(flagKey, defaultValue, options) {
|
|
57
80
|
return useObjectFlagDetails(flagKey, defaultValue, options).value;
|
|
58
81
|
}
|
|
59
82
|
function useObjectFlagDetails(flagKey, defaultValue, options) {
|
|
60
|
-
return attachHandlersAndResolve(
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
return attachHandlersAndResolve(
|
|
84
|
+
flagKey,
|
|
85
|
+
defaultValue,
|
|
86
|
+
(client) => {
|
|
87
|
+
return client.getObjectDetails;
|
|
88
|
+
},
|
|
89
|
+
options
|
|
90
|
+
);
|
|
63
91
|
}
|
|
64
92
|
function attachHandlersAndResolve(flagKey, defaultValue, resolver, options) {
|
|
65
93
|
const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -69,7 +97,13 @@ function attachHandlersAndResolve(flagKey, defaultValue, resolver, options) {
|
|
|
69
97
|
updateState({});
|
|
70
98
|
};
|
|
71
99
|
const suspendRef = () => {
|
|
72
|
-
suspend(
|
|
100
|
+
suspend(
|
|
101
|
+
client,
|
|
102
|
+
updateState,
|
|
103
|
+
ProviderEvents.ContextChanged,
|
|
104
|
+
ProviderEvents.ConfigurationChanged,
|
|
105
|
+
ProviderEvents.Ready
|
|
106
|
+
);
|
|
73
107
|
};
|
|
74
108
|
useEffect(() => {
|
|
75
109
|
if (client.providerStatus === ProviderStatus.NOT_READY) {
|
|
@@ -120,16 +154,13 @@ function suspend(client, updateState, ...resumeEvents) {
|
|
|
120
154
|
function suspenseWrapper(promise) {
|
|
121
155
|
let status = 0 /* Pending */;
|
|
122
156
|
let result;
|
|
123
|
-
const suspended = promise.then(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
result = error;
|
|
131
|
-
}
|
|
132
|
-
);
|
|
157
|
+
const suspended = promise.then((value) => {
|
|
158
|
+
status = 1 /* Success */;
|
|
159
|
+
result = value;
|
|
160
|
+
}).catch((error) => {
|
|
161
|
+
status = 2 /* Error */;
|
|
162
|
+
result = error;
|
|
163
|
+
});
|
|
133
164
|
return () => {
|
|
134
165
|
switch (status) {
|
|
135
166
|
case 0 /* Pending */:
|
|
@@ -143,6 +174,41 @@ function suspenseWrapper(promise) {
|
|
|
143
174
|
}
|
|
144
175
|
};
|
|
145
176
|
}
|
|
177
|
+
var HookFlagQuery = class {
|
|
178
|
+
constructor(_details) {
|
|
179
|
+
this._details = _details;
|
|
180
|
+
}
|
|
181
|
+
get details() {
|
|
182
|
+
return this._details;
|
|
183
|
+
}
|
|
184
|
+
get value() {
|
|
185
|
+
return this._details?.value;
|
|
186
|
+
}
|
|
187
|
+
get variant() {
|
|
188
|
+
return this._details.variant;
|
|
189
|
+
}
|
|
190
|
+
get flagMetadata() {
|
|
191
|
+
return this._details.flagMetadata;
|
|
192
|
+
}
|
|
193
|
+
get reason() {
|
|
194
|
+
return this._details.reason;
|
|
195
|
+
}
|
|
196
|
+
get isError() {
|
|
197
|
+
return !!this._details?.errorCode || this._details.reason == StandardResolutionReasons.ERROR;
|
|
198
|
+
}
|
|
199
|
+
get errorCode() {
|
|
200
|
+
return this._details?.errorCode;
|
|
201
|
+
}
|
|
202
|
+
get errorMessage() {
|
|
203
|
+
return this._details?.errorMessage;
|
|
204
|
+
}
|
|
205
|
+
get isAuthoritative() {
|
|
206
|
+
return !this.isError && this._details.reason != StandardResolutionReasons.STALE && this._details.reason != StandardResolutionReasons.DISABLED;
|
|
207
|
+
}
|
|
208
|
+
get type() {
|
|
209
|
+
return typeof this._details.value;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
146
212
|
|
|
147
213
|
// src/index.ts
|
|
148
214
|
export * from "@openfeature/web-sdk";
|
|
@@ -150,6 +216,7 @@ export {
|
|
|
150
216
|
OpenFeatureProvider,
|
|
151
217
|
useBooleanFlagDetails,
|
|
152
218
|
useBooleanFlagValue,
|
|
219
|
+
useFlag,
|
|
153
220
|
useNumberFlagDetails,
|
|
154
221
|
useNumberFlagValue,
|
|
155
222
|
useObjectFlagDetails,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/use-feature-flag.ts", "../../src/provider.tsx", "../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { Client, EvaluationDetails, FlagEvaluationOptions, FlagValue, JsonValue, 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 show suspense fallbacks until the provider is initialized.\n * Defaults to true.\n */\n suspendUntilReady?: boolean,\n /**\n * Suspend flag evaluations while the provider's context is being reconciled.\n * Set to true if you want to show suspense fallbacks while flags are re-evaluated after context changes.\n * Defaults to false.\n */\n suspendWhileReconciling?: 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 suspendUntilReady: true,\n suspendWhileReconciling: false,\n};\n\nenum SuspendState {\n Pending,\n Success,\n Error\n}\n\n/**\n * Evaluates a feature flag, returning a boolean.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagValue(flagKey: string, defaultValue: boolean, options?: ReactFlagEvaluationOptions): boolean {\n return useBooleanFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<boolean>} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagDetails(flagKey: string, defaultValue: boolean, options?: ReactFlagEvaluationOptions): EvaluationDetails<boolean> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getBooleanDetails;\n }, options);\n}\n\n/**\n * Evaluates a feature flag, returning a string.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagValue<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): T {\n return useStringFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<string>} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagDetails<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getStringDetails<T>;\n }, options);\n}\n\n/**\n * Evaluates a feature flag, returning a number.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagValue<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): T {\n return useNumberFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<number>} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagDetails<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getNumberDetails<T>;\n }, options);\n}\n\n/**\n * Evaluates a feature flag, returning an object.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagValue<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): T {\n return useObjectFlagDetails<T>(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @param {T} defaultValue the default value\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagDetails<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n return attachHandlersAndResolve(flagKey, defaultValue, (client) => {\n return client.getObjectDetails<T>;\n }, options);\n}\n\nfunction attachHandlersAndResolve<T extends FlagValue>(flagKey: string, defaultValue: T, resolver: (client: Client) => (flagKey: string, defaultValue: T) => EvaluationDetails<T>, options?: ReactFlagEvaluationOptions): EvaluationDetails<T> {\n const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };\n const [, updateState] = useState<object | undefined>();\n const client = useOpenFeatureClient();\n const forceUpdate = () => {\n updateState({});\n };\n const suspendRef = () => {\n suspend(client, updateState, ProviderEvents.ContextChanged, ProviderEvents.ConfigurationChanged, ProviderEvents.Ready);\n };\n\n useEffect(() => {\n if (client.providerStatus === ProviderStatus.NOT_READY) {\n // update when the provider is ready\n client.addHandler(ProviderEvents.Ready, forceUpdate);\n if (defaultedOptions.suspendUntilReady) {\n suspend(client, updateState, ProviderEvents.Ready);\n }\n }\n\n if (defaultedOptions.updateOnContextChanged) {\n // update when the context changes\n client.addHandler(ProviderEvents.ContextChanged, forceUpdate);\n if (defaultedOptions.suspendWhileReconciling) {\n client.addHandler(ProviderEvents.Reconciling, suspendRef);\n }\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.Ready, forceUpdate);\n client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);\n client.removeHandler(ProviderEvents.Reconciling, suspendRef);\n };\n }, []);\n \n useEffect(() => {\n if (defaultedOptions.updateOnConfigurationChanged) {\n // update when the provider configuration changes\n client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n };\n }, []);\n\n return resolver(client).call(client, flagKey, defaultValue);\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 * @param {ProviderEvents[]} resumeEvents list of events which will resume the suspend\n */\nfunction suspend(client: Client, updateState: Dispatch<SetStateAction<object | undefined>>, ...resumeEvents: ProviderEvents[]) {\n\n let suspendResolver: () => void;\n \n const suspendPromise = new Promise<void>((resolve) => {\n suspendResolver = () => {\n resolve();\n resumeEvents.forEach((e) => {\n client.removeHandler(e, suspendResolver); // remove handlers once they've run\n });\n client.removeHandler(ProviderEvents.Error, suspendResolver);\n };\n resumeEvents.forEach((e) => {\n client.addHandler(e, suspendResolver);\n });\n client.addHandler(ProviderEvents.Error, suspendResolver); // we never want to throw, resolve with errors - we may make this configurable later\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 ClientOrDomain =\n | {\n /**\n * An identifier which logically binds clients with providers\n * @see OpenFeature.setProvider() and overloads.\n */\n domain?: string;\n client?: never;\n }\n | {\n /**\n * OpenFeature client to use.\n */\n client?: Client;\n domain?: never;\n };\n\ntype ProviderProps = {\n children?: React.ReactNode;\n} & ClientOrDomain;\n\nconst Context = React.createContext<Client | undefined>(undefined);\n\nexport const OpenFeatureProvider = ({ client, domain, children }: ProviderProps) => {\n if (!client) {\n client = OpenFeature.getClient(domain);\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": ";AAAA,
|
|
3
|
+
"sources": ["../../src/evaluation/use-feature-flag.ts", "../../src/provider/provider.tsx", "../../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n Client,\n EvaluationDetails,\n FlagEvaluationOptions,\n FlagValue,\n JsonValue,\n ProviderEvents,\n ProviderStatus,\n StandardResolutionReasons,\n} from '@openfeature/web-sdk';\nimport { Dispatch, SetStateAction, useEffect, useState } from 'react';\nimport { useOpenFeatureClient } from '../provider';\nimport { FlagQuery } from '../query';\n\ntype ReactFlagEvaluationOptions = {\n /**\n * Suspend flag evaluations while the provider is not ready.\n * Set to false if you don't want to show suspense fallbacks until the provider is initialized.\n * Defaults to true.\n */\n suspendUntilReady?: boolean;\n /**\n * Suspend flag evaluations while the provider's context is being reconciled.\n * Set to true if you want to show suspense fallbacks while flags are re-evaluated after context changes.\n * Defaults to false.\n */\n suspendWhileReconciling?: 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 suspendUntilReady: true,\n suspendWhileReconciling: false,\n};\n\nenum SuspendState {\n Pending,\n Success,\n Error,\n}\n\n// This type is a bit wild-looking, but I think we need it.\n// We have to use the conditional, because otherwise useFlag('key', false) would return false, not boolean (too constrained).\n// We have a duplicate for the hook return below, this one is just used for casting because the name isn't as clear\ntype ConstrainedFlagQuery<T> = FlagQuery<\n T extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T extends JsonValue\n ? T\n : JsonValue\n>;\n\n/**\n * Evaluates a feature flag generically, returning an react-flavored queryable object.\n * The resolver method to use is based on the type of the defaultValue.\n * For type-specific hooks, use {@link useBooleanFlagValue}, {@link useBooleanFlagDetails} and equivalents.\n * By default, components will re-render when the flag value changes.\n * @param {string} flagKey the flag identifier\n * @template {FlagValue} T A optional generic argument constraining the default.\n * @param {T} defaultValue the default value; used to determine what resolved type should be used.\n * @param {ReactFlagEvaluationOptions} options for this evaluation\n * @returns { FlagQuery } a queryable object containing useful information about the flag.\n */\nexport function useFlag<T extends FlagValue = FlagValue>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): FlagQuery<\nT extends boolean\n ? boolean\n : T extends number\n ? number\n : T extends string\n ? string\n : T extends JsonValue\n ? T\n : JsonValue\n> {\n // use the default value to determine the resolver to call\n const query =\n typeof defaultValue === 'boolean'\n ? new HookFlagQuery<boolean>(useBooleanFlagDetails(flagKey, defaultValue, options))\n : typeof defaultValue === 'number'\n ? new HookFlagQuery<number>(useNumberFlagDetails(flagKey, defaultValue, options))\n : typeof defaultValue === 'string'\n ? new HookFlagQuery<string>(useStringFlagDetails(flagKey, defaultValue, options))\n : new HookFlagQuery<JsonValue>(useObjectFlagDetails(flagKey, defaultValue, options));\n // TS sees this as HookFlagQuery<JsonValue>, because the compiler isn't aware of the `typeof` checks above.\n return query as unknown as ConstrainedFlagQuery<T>;\n}\n\n/**\n * Evaluates a feature flag, returning a boolean.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagValue(\n flagKey: string,\n defaultValue: boolean,\n options?: ReactFlagEvaluationOptions,\n): boolean {\n return useBooleanFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @param {boolean} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<boolean>} a EvaluationDetails object for this evaluation\n */\nexport function useBooleanFlagDetails(\n flagKey: string,\n defaultValue: boolean,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<boolean> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getBooleanDetails;\n },\n options,\n );\n}\n\n/**\n * Evaluates a feature flag, returning a string.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagValue<T extends string = string>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): string {\n return useStringFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {string} [T=string] A optional generic argument constraining the string\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<string>} a EvaluationDetails object for this evaluation\n */\nexport function useStringFlagDetails<T extends string = string>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<string> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getStringDetails<T>;\n },\n options,\n );\n}\n\n/**\n * Evaluates a feature flag, returning a number.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagValue<T extends number = number>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): number {\n return useNumberFlagDetails(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {number} [T=number] A optional generic argument constraining the number\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<number>} a EvaluationDetails object for this evaluation\n */\nexport function useNumberFlagDetails<T extends number = number>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<number> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getNumberDetails<T>;\n },\n options,\n );\n}\n\n/**\n * Evaluates a feature flag, returning an object.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {T} defaultValue the default value\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { boolean} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagValue<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): T {\n return useObjectFlagDetails<T>(flagKey, defaultValue, options).value;\n}\n\n/**\n * Evaluates a feature flag, returning evaluation details.\n * By default, components will re-render when the flag value changes.\n * For a generic hook returning a queryable interface, see {@link useFlag}.\n * @param {string} flagKey the flag identifier\n * @param {T} defaultValue the default value\n * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure\n * @param {ReactFlagEvaluationOptions} options options for this evaluation\n * @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation\n */\nexport function useObjectFlagDetails<T extends JsonValue = JsonValue>(\n flagKey: string,\n defaultValue: T,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<T> {\n return attachHandlersAndResolve(\n flagKey,\n defaultValue,\n (client) => {\n return client.getObjectDetails<T>;\n },\n options,\n );\n}\n\nfunction attachHandlersAndResolve<T extends FlagValue>(\n flagKey: string,\n defaultValue: T,\n resolver: (client: Client) => (flagKey: string, defaultValue: T) => EvaluationDetails<T>,\n options?: ReactFlagEvaluationOptions,\n): EvaluationDetails<T> {\n const defaultedOptions = { ...DEFAULT_OPTIONS, ...options };\n const [, updateState] = useState<object | undefined>();\n const client = useOpenFeatureClient();\n const forceUpdate = () => {\n updateState({});\n };\n const suspendRef = () => {\n suspend(\n client,\n updateState,\n ProviderEvents.ContextChanged,\n ProviderEvents.ConfigurationChanged,\n ProviderEvents.Ready,\n );\n };\n\n useEffect(() => {\n if (client.providerStatus === ProviderStatus.NOT_READY) {\n // update when the provider is ready\n client.addHandler(ProviderEvents.Ready, forceUpdate);\n if (defaultedOptions.suspendUntilReady) {\n suspend(client, updateState, ProviderEvents.Ready);\n }\n }\n\n if (defaultedOptions.updateOnContextChanged) {\n // update when the context changes\n client.addHandler(ProviderEvents.ContextChanged, forceUpdate);\n if (defaultedOptions.suspendWhileReconciling) {\n client.addHandler(ProviderEvents.Reconciling, suspendRef);\n }\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.Ready, forceUpdate);\n client.removeHandler(ProviderEvents.ContextChanged, forceUpdate);\n client.removeHandler(ProviderEvents.Reconciling, suspendRef);\n };\n }, []);\n\n useEffect(() => {\n if (defaultedOptions.updateOnConfigurationChanged) {\n // update when the provider configuration changes\n client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n }\n return () => {\n // cleanup the handlers\n client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate);\n };\n }, []);\n\n return resolver(client).call(client, flagKey, defaultValue);\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 * @param {ProviderEvents[]} resumeEvents list of events which will resume the suspend\n */\nfunction suspend(\n client: Client,\n updateState: Dispatch<SetStateAction<object | undefined>>,\n ...resumeEvents: ProviderEvents[]\n) {\n let suspendResolver: () => void;\n\n const suspendPromise = new Promise<void>((resolve) => {\n suspendResolver = () => {\n resolve();\n resumeEvents.forEach((e) => {\n client.removeHandler(e, suspendResolver); // remove handlers once they've run\n });\n client.removeHandler(ProviderEvents.Error, suspendResolver);\n };\n resumeEvents.forEach((e) => {\n client.addHandler(e, suspendResolver);\n });\n client.addHandler(ProviderEvents.Error, suspendResolver); // we never want to throw, resolve with errors - we may make this configurable later\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\n .then((value) => {\n status = SuspendState.Success;\n result = value;\n })\n .catch((error) => {\n status = SuspendState.Error;\n result = error;\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}\n\n// FlagQuery implementation, do not export\nclass HookFlagQuery<T extends FlagValue = FlagValue> implements FlagQuery {\n constructor(private _details: EvaluationDetails<T>) {}\n\n get details() {\n return this._details;\n }\n\n get value() {\n return this._details?.value;\n }\n\n get variant() {\n return this._details.variant;\n }\n\n get flagMetadata() {\n return this._details.flagMetadata;\n }\n\n get reason() {\n return this._details.reason;\n }\n\n get isError() {\n return !!this._details?.errorCode || this._details.reason == StandardResolutionReasons.ERROR;\n }\n\n get errorCode() {\n return this._details?.errorCode;\n }\n\n get errorMessage() {\n return this._details?.errorMessage;\n }\n\n get isAuthoritative() {\n return (\n !this.isError &&\n this._details.reason != StandardResolutionReasons.STALE &&\n this._details.reason != StandardResolutionReasons.DISABLED\n );\n }\n\n get type() {\n return typeof this._details.value;\n }\n}\n", "import * as React from 'react';\nimport { Client, OpenFeature } from '@openfeature/web-sdk';\n\ntype ClientOrDomain =\n | {\n /**\n * An identifier which logically binds clients with providers\n * @see OpenFeature.setProvider() and overloads.\n */\n domain?: string;\n client?: never;\n }\n | {\n /**\n * OpenFeature client to use.\n */\n client?: Client;\n domain?: never;\n };\n\ntype ProviderProps = {\n children?: React.ReactNode;\n} & ClientOrDomain;\n\nconst Context = React.createContext<Client | undefined>(undefined);\n\nexport const OpenFeatureProvider = ({ client, domain, children }: ProviderProps) => {\n if (!client) {\n client = OpenFeature.getClient(domain);\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 './evaluation';\nexport * from './query';\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
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,56 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FlagValue as FlagValue$1, JsonValue, EvaluationDetails as EvaluationDetails$1, FlagEvaluationOptions, Client } from '@openfeature/web-sdk';
|
|
2
2
|
export * from '@openfeature/web-sdk';
|
|
3
|
+
import { FlagValue, EvaluationDetails, FlagMetadata, StandardResolutionReasons, ErrorCode } from '@openfeature/core';
|
|
3
4
|
import * as React from 'react';
|
|
4
5
|
|
|
6
|
+
interface FlagQuery<T extends FlagValue = FlagValue> {
|
|
7
|
+
/**
|
|
8
|
+
* A structure representing the result of the flag evaluation process
|
|
9
|
+
*/
|
|
10
|
+
readonly details: EvaluationDetails<T>;
|
|
11
|
+
/**
|
|
12
|
+
* The flag value
|
|
13
|
+
*/
|
|
14
|
+
readonly value: T;
|
|
15
|
+
/**
|
|
16
|
+
* A variant is a semantic identifier for a value.
|
|
17
|
+
* Not available from all providers.
|
|
18
|
+
*/
|
|
19
|
+
readonly variant: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Arbitrary data associated with this flag or evaluation.
|
|
22
|
+
* Not available from all providers.
|
|
23
|
+
*/
|
|
24
|
+
readonly flagMetadata: FlagMetadata;
|
|
25
|
+
/**
|
|
26
|
+
* The reason the evaluation resolved to the particular value.
|
|
27
|
+
* Not available from all providers.
|
|
28
|
+
*/
|
|
29
|
+
readonly reason: typeof StandardResolutionReasons | string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Indicates if this flag defaulted due to an error.
|
|
32
|
+
* Specifically, indicates reason equals {@link StandardResolutionReasons.ERROR} or the errorCode is set, this field is truthy.
|
|
33
|
+
*/
|
|
34
|
+
readonly isError: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The error code, see {@link ErrorCode}.
|
|
37
|
+
*/
|
|
38
|
+
readonly errorCode: ErrorCode | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* A message associated with the error.
|
|
41
|
+
*/
|
|
42
|
+
readonly errorMessage: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Indicates this flag is up-to-date and in sync with the source of truth.
|
|
45
|
+
* Specifically, indicates the evaluation did not default due to error, and the reason is neither {@link StandardResolutionReasons.STALE} or {@link StandardResolutionReasons.DISABLED}.
|
|
46
|
+
*/
|
|
47
|
+
readonly isAuthoritative: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The type of the value, as returned by "typeof" operator.
|
|
50
|
+
*/
|
|
51
|
+
readonly type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';
|
|
52
|
+
}
|
|
53
|
+
|
|
5
54
|
type ReactFlagEvaluationOptions = {
|
|
6
55
|
/**
|
|
7
56
|
* Suspend flag evaluations while the provider is not ready.
|
|
@@ -30,9 +79,22 @@ type ReactFlagEvaluationOptions = {
|
|
|
30
79
|
*/
|
|
31
80
|
updateOnContextChanged?: boolean;
|
|
32
81
|
} & FlagEvaluationOptions;
|
|
82
|
+
/**
|
|
83
|
+
* Evaluates a feature flag generically, returning an react-flavored queryable object.
|
|
84
|
+
* The resolver method to use is based on the type of the defaultValue.
|
|
85
|
+
* For type-specific hooks, use {@link useBooleanFlagValue}, {@link useBooleanFlagDetails} and equivalents.
|
|
86
|
+
* By default, components will re-render when the flag value changes.
|
|
87
|
+
* @param {string} flagKey the flag identifier
|
|
88
|
+
* @template {FlagValue} T A optional generic argument constraining the default.
|
|
89
|
+
* @param {T} defaultValue the default value; used to determine what resolved type should be used.
|
|
90
|
+
* @param {ReactFlagEvaluationOptions} options for this evaluation
|
|
91
|
+
* @returns { FlagQuery } a queryable object containing useful information about the flag.
|
|
92
|
+
*/
|
|
93
|
+
declare function useFlag<T extends FlagValue$1 = FlagValue$1>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): FlagQuery<T extends boolean ? boolean : T extends number ? number : T extends string ? string : T extends JsonValue ? T : JsonValue>;
|
|
33
94
|
/**
|
|
34
95
|
* Evaluates a feature flag, returning a boolean.
|
|
35
96
|
* By default, components will re-render when the flag value changes.
|
|
97
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
36
98
|
* @param {string} flagKey the flag identifier
|
|
37
99
|
* @param {boolean} defaultValue the default value
|
|
38
100
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
@@ -42,55 +104,61 @@ declare function useBooleanFlagValue(flagKey: string, defaultValue: boolean, opt
|
|
|
42
104
|
/**
|
|
43
105
|
* Evaluates a feature flag, returning evaluation details.
|
|
44
106
|
* By default, components will re-render when the flag value changes.
|
|
107
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
45
108
|
* @param {string} flagKey the flag identifier
|
|
46
109
|
* @param {boolean} defaultValue the default value
|
|
47
110
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
48
111
|
* @returns { EvaluationDetails<boolean>} a EvaluationDetails object for this evaluation
|
|
49
112
|
*/
|
|
50
|
-
declare function useBooleanFlagDetails(flagKey: string, defaultValue: boolean, options?: ReactFlagEvaluationOptions): EvaluationDetails<boolean>;
|
|
113
|
+
declare function useBooleanFlagDetails(flagKey: string, defaultValue: boolean, options?: ReactFlagEvaluationOptions): EvaluationDetails$1<boolean>;
|
|
51
114
|
/**
|
|
52
115
|
* Evaluates a feature flag, returning a string.
|
|
53
116
|
* By default, components will re-render when the flag value changes.
|
|
117
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
54
118
|
* @param {string} flagKey the flag identifier
|
|
55
119
|
* @template {string} [T=string] A optional generic argument constraining the string
|
|
56
120
|
* @param {T} defaultValue the default value
|
|
57
121
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
58
122
|
* @returns { boolean} a EvaluationDetails object for this evaluation
|
|
59
123
|
*/
|
|
60
|
-
declare function useStringFlagValue<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions):
|
|
124
|
+
declare function useStringFlagValue<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): string;
|
|
61
125
|
/**
|
|
62
126
|
* Evaluates a feature flag, returning evaluation details.
|
|
63
127
|
* By default, components will re-render when the flag value changes.
|
|
128
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
64
129
|
* @param {string} flagKey the flag identifier
|
|
65
130
|
* @template {string} [T=string] A optional generic argument constraining the string
|
|
66
131
|
* @param {T} defaultValue the default value
|
|
67
132
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
68
133
|
* @returns { EvaluationDetails<string>} a EvaluationDetails object for this evaluation
|
|
69
134
|
*/
|
|
70
|
-
declare function useStringFlagDetails<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<
|
|
135
|
+
declare function useStringFlagDetails<T extends string = string>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails$1<string>;
|
|
71
136
|
/**
|
|
72
137
|
* Evaluates a feature flag, returning a number.
|
|
73
138
|
* By default, components will re-render when the flag value changes.
|
|
139
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
74
140
|
* @param {string} flagKey the flag identifier
|
|
75
141
|
* @template {number} [T=number] A optional generic argument constraining the number
|
|
76
142
|
* @param {T} defaultValue the default value
|
|
77
143
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
78
144
|
* @returns { boolean} a EvaluationDetails object for this evaluation
|
|
79
145
|
*/
|
|
80
|
-
declare function useNumberFlagValue<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions):
|
|
146
|
+
declare function useNumberFlagValue<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): number;
|
|
81
147
|
/**
|
|
82
148
|
* Evaluates a feature flag, returning evaluation details.
|
|
83
149
|
* By default, components will re-render when the flag value changes.
|
|
150
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
84
151
|
* @param {string} flagKey the flag identifier
|
|
85
152
|
* @template {number} [T=number] A optional generic argument constraining the number
|
|
86
153
|
* @param {T} defaultValue the default value
|
|
87
154
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
88
155
|
* @returns { EvaluationDetails<number>} a EvaluationDetails object for this evaluation
|
|
89
156
|
*/
|
|
90
|
-
declare function useNumberFlagDetails<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<
|
|
157
|
+
declare function useNumberFlagDetails<T extends number = number>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails$1<number>;
|
|
91
158
|
/**
|
|
92
159
|
* Evaluates a feature flag, returning an object.
|
|
93
160
|
* By default, components will re-render when the flag value changes.
|
|
161
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
94
162
|
* @param {string} flagKey the flag identifier
|
|
95
163
|
* @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure
|
|
96
164
|
* @param {T} defaultValue the default value
|
|
@@ -101,13 +169,14 @@ declare function useObjectFlagValue<T extends JsonValue = JsonValue>(flagKey: st
|
|
|
101
169
|
/**
|
|
102
170
|
* Evaluates a feature flag, returning evaluation details.
|
|
103
171
|
* By default, components will re-render when the flag value changes.
|
|
172
|
+
* For a generic hook returning a queryable interface, see {@link useFlag}.
|
|
104
173
|
* @param {string} flagKey the flag identifier
|
|
105
174
|
* @param {T} defaultValue the default value
|
|
106
175
|
* @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure
|
|
107
176
|
* @param {ReactFlagEvaluationOptions} options options for this evaluation
|
|
108
177
|
* @returns { EvaluationDetails<T>} a EvaluationDetails object for this evaluation
|
|
109
178
|
*/
|
|
110
|
-
declare function useObjectFlagDetails<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails<T>;
|
|
179
|
+
declare function useObjectFlagDetails<T extends JsonValue = JsonValue>(flagKey: string, defaultValue: T, options?: ReactFlagEvaluationOptions): EvaluationDetails$1<T>;
|
|
111
180
|
|
|
112
181
|
type ClientOrDomain = {
|
|
113
182
|
/**
|
|
@@ -129,4 +198,4 @@ type ProviderProps = {
|
|
|
129
198
|
declare const OpenFeatureProvider: ({ client, domain, children }: ProviderProps) => React.JSX.Element;
|
|
130
199
|
declare const useOpenFeatureClient: () => Client;
|
|
131
200
|
|
|
132
|
-
export { OpenFeatureProvider, useBooleanFlagDetails, useBooleanFlagValue, useNumberFlagDetails, useNumberFlagValue, useObjectFlagDetails, useObjectFlagValue, useOpenFeatureClient, useStringFlagDetails, useStringFlagValue };
|
|
201
|
+
export { FlagQuery, OpenFeatureProvider, useBooleanFlagDetails, useBooleanFlagValue, useFlag, useNumberFlagDetails, useNumberFlagValue, useObjectFlagDetails, useObjectFlagValue, useOpenFeatureClient, useStringFlagDetails, useStringFlagValue };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfeature/react-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4-experimental",
|
|
4
4
|
"description": "OpenFeature React SDK",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"files": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://github.com/open-feature/js-sdk#readme",
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@openfeature/web-sdk": "
|
|
49
|
+
"@openfeature/web-sdk": "^1.0.2",
|
|
50
50
|
"react": ">=16.8.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|