@spotify-confidence/openfeature-server-provider-local 0.6.0 → 0.8.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.7.0...openfeature-provider-js-v0.8.0) (2026-01-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **js:** add React support with useFlag and useFlagDetails hooks ([#246](https://github.com/spotify/confidence-resolver/issues/246)) ([d579a4c](https://github.com/spotify/confidence-resolver/commit/d579a4c8fe493ee3a92539203f21d1c03758c58e))
9
+ * **wasm:** add wasm API to apply previously resolved flags ([#235](https://github.com/spotify/confidence-resolver/issues/235)) ([79048f6](https://github.com/spotify/confidence-resolver/commit/79048f63a8c771eb98ecf478cab0b654aa745374))
10
+
11
+
12
+ ### Dependencies
13
+
14
+ * The following workspace dependencies were updated
15
+ * dependencies
16
+ * rust-guest bumped from 0.1.13 to 0.1.14
17
+
18
+ ## [0.7.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.6.0...openfeature-provider-js-v0.7.0) (2026-01-22)
19
+
20
+
21
+ ### Features
22
+
23
+ * inlined WASM for improved portability ([#243](https://github.com/spotify/confidence-resolver/issues/243)) ([8d86283](https://github.com/spotify/confidence-resolver/commit/8d862837244ffd9099bfeb56b42e203212e73a96))
24
+
25
+
26
+ ### Dependencies
27
+
28
+ * The following workspace dependencies were updated
29
+ * dependencies
30
+ * rust-guest bumped from 0.1.12 to 0.1.13
31
+
3
32
  ## [0.6.0](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.5.1...openfeature-provider-js-v0.6.0) (2026-01-15)
4
33
 
5
34
 
package/README.md CHANGED
@@ -3,12 +3,15 @@
3
3
  OpenFeature provider for the Spotify Confidence resolver (local mode, powered by WebAssembly). It periodically fetches resolver state, evaluates flags locally, and flushes evaluation logs to the Confidence backend.
4
4
 
5
5
  ## Features
6
+
6
7
  - Local flag evaluation via WASM (no per-eval network calls)
7
8
  - Automatic state refresh and batched flag log flushing
8
9
  - Pluggable `fetch` with retries, timeouts and routing
9
10
  - Optional logging using `debug`
11
+ - **[React integration](./README-REACT.md)** for Next.js with Server Components
10
12
 
11
13
  ## Requirements
14
+
12
15
  - Node.js 18+ (built-in `fetch`) or provide a compatible `fetch`
13
16
  - WebAssembly support (Node 18+/modern browsers)
14
17
 
@@ -24,6 +27,7 @@ yarn add debug
24
27
  ```
25
28
 
26
29
  Notes:
30
+
27
31
  - `debug` is an optional peer. Install it if you want logs. Without it, logging is a no-op.
28
32
  - Types and bundling are ESM-first; Node is supported, and a browser build is provided for modern bundlers.
29
33
 
@@ -34,6 +38,7 @@ Notes:
34
38
  You'll need a **client secret** from Confidence to use this provider.
35
39
 
36
40
  **📖 See the [Integration Guide: Getting Your Credentials](../INTEGRATION_GUIDE.md#getting-your-credentials)** for step-by-step instructions on:
41
+
37
42
  - How to navigate the Confidence dashboard
38
43
  - Creating a Backend integration
39
44
  - Creating a test flag for verification
@@ -104,6 +109,7 @@ const context = {
104
109
  The provider uses a **default value fallback** pattern - when evaluation fails, it returns your specified default value instead of throwing an error.
105
110
 
106
111
  **📖 See the [Integration Guide: Error Handling](../INTEGRATION_GUIDE.md#error-handling)** for:
112
+
107
113
  - Common failure scenarios
108
114
  - Error codes and meanings
109
115
  - Production best practices
@@ -119,8 +125,8 @@ const enabled = await client.getBooleanValue('my-flag.enabled', false, context);
119
125
  // For detailed error information, use getBooleanDetails()
120
126
  const details = await client.getBooleanDetails('my-flag.enabled', false, context);
121
127
  if (details.errorCode) {
122
- console.error('Flag evaluation error:', details.errorMessage);
123
- console.log('Reason:', details.reason);
128
+ console.error('Flag evaluation error:', details.errorMessage);
129
+ console.log('Reason:', details.reason);
124
130
  }
125
131
  ```
126
132
 
@@ -135,11 +141,85 @@ if (details.errorCode) {
135
141
  - `fetch` (optional): Custom `fetch` implementation. Required for Node < 18; for Node 18+ you can omit.
136
142
 
137
143
  The provider periodically:
144
+
138
145
  - Refreshes resolver state (configurable via `stateUpdateInterval`, default every 30s)
139
146
  - Flushes flag evaluation logs to the backend (configurable via `flushInterval`, default every 10s)
140
147
 
141
148
  ---
142
149
 
150
+ ## Exports and WASM Loading
151
+
152
+ The package provides multiple exports for different environments:
153
+
154
+ ### Default export (recommended)
155
+
156
+ ```ts
157
+ import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local';
158
+ ```
159
+
160
+ The WASM is **inlined as a data URL** — this is the most portable option and should work across virtually all environments. The tradeoff is a larger bundle (~700kB), but this isn't a problem for the intended server-side usage.
161
+
162
+ No configuration needed.
163
+
164
+ ### `./node` — Traditional Node.js
165
+
166
+ ```ts
167
+ import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local/node';
168
+ ```
169
+
170
+ Uses `fs.readFile()` to load WASM from the installed package. Works well in a regular Node.js environment with node_modules.
171
+
172
+ You can customize the WASM path if needed:
173
+
174
+ ```ts
175
+ const provider = createConfidenceServerProvider({
176
+ flagClientSecret: '...',
177
+ wasmPath: '/custom/path/to/confidence_resolver.wasm',
178
+ });
179
+ ```
180
+
181
+ ### `./fetch` — Modern and standards compliant environments
182
+
183
+ ```ts
184
+ import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local/fetch';
185
+ ```
186
+
187
+ Uses `fetch()` with `import.meta.url` to load WASM. Works in Deno, Bun, and browsers with bundlers that properly handle asset URLs (Vite, Rollup, etc.).
188
+
189
+ You can customize the WASM URL if needed:
190
+
191
+ ```ts
192
+ const provider = createConfidenceServerProvider({
193
+ flagClientSecret: '...',
194
+ wasmUrl: '/assets/confidence_resolver.wasm',
195
+ });
196
+ ```
197
+
198
+ ### `./react-server` and `./react-client` — React/Next.js Integration
199
+
200
+ ```ts
201
+ // Server Component
202
+ import { ConfidenceProvider, getFlag, getFlagDetails } from '@spotify-confidence/openfeature-server-provider-local/react-server';
203
+
204
+ // Client Component
205
+ import { useFlag, useFlagDetails } from '@spotify-confidence/openfeature-server-provider-local/react-client';
206
+ ```
207
+
208
+ React hooks and components for Next.js App Router with Server Components. Flags are resolved on the server and provided to client components via React Context.
209
+
210
+ **See [README-REACT.md](./README-REACT.md) for full documentation.**
211
+
212
+ ### A note on browser usage
213
+
214
+ While browsers are mentioned in this doc, this package is intended for server-side use only. Two concerns for browser usage:
215
+
216
+ - Size: The WASM+JS is currently ~270kb gzipped, too large for typical client bundles.
217
+ - Security: In a browser, all flag rules and variants are exposed to users.
218
+
219
+ That said, the package does work in browsers, and there may be specialized use cases where these tradeoffs are acceptable.
220
+
221
+ ---
222
+
143
223
  ## Materialization Stores
144
224
 
145
225
  Materialization stores provide persistent storage for sticky variant assignments and custom targeting segments. This enables two key use cases:
@@ -164,11 +244,13 @@ const provider = createConfidenceServerProvider({
164
244
  ```
165
245
 
166
246
  **When to use**:
247
+
167
248
  - You need sticky assignments or materialized segments but don't want to manage storage infrastructure
168
249
  - Quick prototyping or getting started
169
250
  - Lower-volume applications where network latency is acceptable
170
251
 
171
252
  **Trade-offs**:
253
+
172
254
  - Additional network calls during flag resolution (adds latency)
173
255
  - Lower performance compared to local storage implementations (Redis, DynamoDB, etc.)
174
256
 
@@ -200,6 +282,7 @@ For read-only stores (e.g., pre-populated materialized segments without sticky a
200
282
  ### When to Use Materialization Stores
201
283
 
202
284
  Consider implementing a materialization store if:
285
+
203
286
  - You need to support sticky variant assignments for experiments
204
287
  - You use materialized segments for custom targeting
205
288
  - You want to minimize network latency during flag resolution
@@ -214,10 +297,12 @@ If you don't use sticky assignments or materialized segments, the default behavi
214
297
  Logging uses the `debug` library if present; otherwise, all log calls are no-ops.
215
298
 
216
299
  Namespaces:
300
+
217
301
  - Core: `cnfd:*`
218
302
  - Fetch/middleware: `cnfd:fetch:*` (e.g. retries, auth renewals, request summaries)
219
303
 
220
304
  Log levels are hierarchical:
305
+
221
306
  - `cnfd:debug` enables debug, info, warn, and error
222
307
  - `cnfd:info` enables info, warn, and error
223
308
  - `cnfd:warn` enables warn and error
@@ -226,6 +311,7 @@ Log levels are hierarchical:
226
311
  Enable logs:
227
312
 
228
313
  - Node:
314
+
229
315
  ```bash
230
316
  DEBUG=cnfd:* node app.js
231
317
  # or narrower
@@ -233,6 +319,7 @@ DEBUG=cnfd:info,cnfd:fetch:* node app.js
233
319
  ```
234
320
 
235
321
  - Browser (in DevTools console):
322
+
236
323
  ```js
237
324
  localStorage.debug = 'cnfd:*';
238
325
  ```
@@ -245,19 +332,6 @@ yarn add debug
245
332
 
246
333
  ---
247
334
 
248
- ## WebAssembly asset notes
249
-
250
- - Node: the WASM (`confidence_resolver.wasm`) is resolved from the installed package automatically; no extra config needed.
251
- - Browser: the ESM build resolves the WASM via `new URL('confidence_resolver.wasm', import.meta.url)` so modern bundlers (Vite/Rollup/Webpack 5 asset modules) will include it. If your bundler does not, configure it to treat the `.wasm` file as a static asset.
252
-
253
- ---
254
-
255
- ## Using in browsers
256
-
257
- The package exports a browser ESM build that compiles the WASM via streaming and uses the global `fetch`. Integrate it with your OpenFeature SDK variant for the web similarly to Node, then register the provider before evaluation. Credentials must be available to the runtime (e.g. through your app’s configuration layer).
258
-
259
- ---
260
-
261
335
  ## Testing
262
336
 
263
337
  - You can inject a custom `fetch` via the `fetch` option to stub network behavior in tests.
@@ -0,0 +1,129 @@
1
+ import { EvaluationDetails, FlagValue } from "@openfeature/core";
2
+
3
+ //#region src/types.d.ts
4
+ type ResolutionReason = "ERROR" | "FLAG_ARCHIVED" | "MATCH" | "NO_SEGMENT_MATCH" | "TARGETING_KEY_ERROR" | "NO_TREATMENT_MATCH" | "UNSPECIFIED";
5
+ declare enum ErrorCode {
6
+ PROVIDER_NOT_READY = "PROVIDER_NOT_READY",
7
+ PROVIDER_FATAL = "PROVIDER_FATAL",
8
+ FLAG_NOT_FOUND = "FLAG_NOT_FOUND",
9
+ TYPE_MISMATCH = "TYPE_MISMATCH",
10
+ GENERAL = "GENERAL",
11
+ }
12
+ interface ResolutionDetails<T> {
13
+ reason: ResolutionReason;
14
+ value: T;
15
+ variant?: string;
16
+ errorCode?: ErrorCode;
17
+ errorMessage?: string;
18
+ shouldApply: boolean;
19
+ }
20
+ type FlagPrimitive = null | boolean | string | number;
21
+ type FlagObject = {
22
+ [key: string]: FlagValue$1;
23
+ };
24
+ type FlagValue$1 = FlagPrimitive | FlagObject;
25
+ //#endregion
26
+ //#region src/flag-bundle.d.ts
27
+ interface FlagBundle {
28
+ flags: Record<string, ResolutionDetails<FlagObject | null> | undefined>;
29
+ resolveId: string;
30
+ resolveToken: string;
31
+ errorCode?: ErrorCode;
32
+ errorMessage?: string;
33
+ }
34
+ //#endregion
35
+ //#region src/react/client.d.ts
36
+ type FlagBundle$1 = FlagBundle;
37
+ type ApplyFn = (flagName: string) => Promise<void>;
38
+ /** @internal */
39
+ interface ConfidenceClientProviderProps {
40
+ bundle: FlagBundle$1;
41
+ apply: ApplyFn;
42
+ children: React.ReactNode;
43
+ }
44
+ /** @internal */
45
+ declare function ConfidenceClientProvider({
46
+ bundle,
47
+ apply,
48
+ children
49
+ }: ConfidenceClientProviderProps): React.ReactElement;
50
+ interface UseFlagOptions {
51
+ /** Set to false for manual exposure control. Default is true (auto-expose on mount). */
52
+ expose?: boolean;
53
+ }
54
+ /**
55
+ * Details returned by useFlagDetails hook.
56
+ * Always includes an expose function for manual exposure logging.
57
+ */
58
+ interface ClientEvaluationDetails<T extends FlagValue> extends EvaluationDetails<T> {
59
+ /** Function to manually log exposure. No-op if already auto-exposed or if called multiple times. */
60
+ expose: () => void;
61
+ }
62
+ /**
63
+ * React hook for accessing Confidence feature flag values.
64
+ *
65
+ * Automatically logs exposure when the component mounts.
66
+ * Supports dot notation to access nested properties within a flag value.
67
+ *
68
+ * @param flagKey - The flag key, optionally with dot notation for nested access (e.g., 'my-flag.config.enabled')
69
+ * @param defaultValue - Default value if flag or nested property is not found
70
+ * @returns The flag value (or nested property value)
71
+ *
72
+ * @example Basic usage
73
+ * ```tsx
74
+ * const enabled = useFlag('my-feature', false);
75
+ * ```
76
+ *
77
+ * @example Dot notation for nested properties
78
+ * ```tsx
79
+ * // Flag value: { config: { maxItems: 10, enabled: true } }
80
+ * const maxItems = useFlag('my-feature.config.maxItems', 5);
81
+ * const enabled = useFlag('my-feature.config.enabled', false);
82
+ * ```
83
+ *
84
+ * @see useFlagDetails for manual exposure control
85
+ */
86
+ declare function useFlag<T extends FlagValue>(flagKey: string, defaultValue: T): T;
87
+ /**
88
+ * React hook for accessing Confidence feature flag values with full details.
89
+ *
90
+ * Returns the flag value along with variant, reason, and error information.
91
+ * By default, automatically logs exposure when the component mounts.
92
+ * Use `{ expose: false }` for manual exposure control.
93
+ *
94
+ * Supports dot notation to access nested properties within a flag value.
95
+ *
96
+ * @param flagKey - The flag key, optionally with dot notation for nested access (e.g., 'my-flag.config.enabled')
97
+ * @param defaultValue - Default value if flag or nested property is not found
98
+ * @param options - Use `{ expose: false }` for manual exposure control
99
+ * @returns EvaluationDetails with value, flagKey, flagMetadata, variant, reason, errorCode, errorMessage, and expose function.
100
+ *
101
+ * @example Auto exposure with full details
102
+ * ```tsx
103
+ * const { value, variant, reason } = useFlagDetails('my-feature', false);
104
+ * console.log(`Got ${value} from variant ${variant}, reason: ${reason}`);
105
+ * ```
106
+ *
107
+ * @example Manual exposure
108
+ * ```tsx
109
+ * const { value: enabled, expose } = useFlagDetails('my-feature', false, { expose: false });
110
+ *
111
+ * const handleClick = () => {
112
+ * if (enabled) {
113
+ * expose(); // Log exposure only when user interacts
114
+ * doSomething();
115
+ * }
116
+ * };
117
+ * ```
118
+ *
119
+ * @example Error handling
120
+ * ```tsx
121
+ * const { value, errorCode } = useFlagDetails('my-feature', false);
122
+ * if (errorCode === 'FLAG_NOT_FOUND') {
123
+ * console.warn('Flag not configured');
124
+ * }
125
+ * ```
126
+ */
127
+ declare function useFlagDetails<T extends FlagValue>(flagKey: string, defaultValue: T, options?: UseFlagOptions): ClientEvaluationDetails<T>;
128
+ //#endregion
129
+ export { ClientEvaluationDetails, ConfidenceClientProvider, ConfidenceClientProviderProps, useFlag, useFlagDetails };
package/dist/client.js ADDED
@@ -0,0 +1,193 @@
1
+ "use client";
2
+ import { createContext, useCallback, useContext, useEffect, useRef } from "react";
3
+ import "@bufbuild/protobuf/wire";
4
+ import { jsx } from "react/jsx-runtime";
5
+ const NOOP_LOG_FN = Object.assign(() => {}, { enabled: false });
6
+ const debugBackend = loadDebug();
7
+ const logger = new class LoggerImpl {
8
+ childLoggers = /* @__PURE__ */ new Map();
9
+ debug = NOOP_LOG_FN;
10
+ info = NOOP_LOG_FN;
11
+ warn = NOOP_LOG_FN;
12
+ error = NOOP_LOG_FN;
13
+ constructor(name) {
14
+ this.name = name;
15
+ this.configure();
16
+ }
17
+ async configure(backend = debugBackend) {
18
+ const debug = await backend;
19
+ if (!debug) return;
20
+ const debugFn = this.debug = (debug(this.name + ":debug"));
21
+ const infoFn = this.info = (debug(this.name + ":info"));
22
+ const warnFn = this.warn = (debug(this.name + ":warn"));
23
+ const errorFn = this.error = (debug(this.name + ":error"));
24
+ switch (true) {
25
+ case debugFn.enabled: infoFn.enabled = true;
26
+ case infoFn.enabled: warnFn.enabled = true;
27
+ case warnFn.enabled: errorFn.enabled = true;
28
+ }
29
+ }
30
+ getLogger(name) {
31
+ let child = (this.childLoggers.get(name));
32
+ if (!child) {
33
+ child = new LoggerImpl(this.name + ":" + name);
34
+ this.childLoggers.set(name, child);
35
+ }
36
+ return child;
37
+ }
38
+ }("cnfd");
39
+ logger.getLogger.bind(logger);
40
+ async function loadDebug() {
41
+ try {
42
+ const { default: debug } = await import("debug");
43
+ if (typeof debug !== "function") return null;
44
+ return debug;
45
+ } catch (e) {
46
+ return null;
47
+ }
48
+ }
49
+ function devWarn(message) {
50
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") console.warn(message);
51
+ }
52
+ function hasKey(obj, key) {
53
+ return key in obj;
54
+ }
55
+ let ErrorCode = /* @__PURE__ */ function(ErrorCode$1) {
56
+ ErrorCode$1["PROVIDER_NOT_READY"] = "PROVIDER_NOT_READY";
57
+ ErrorCode$1["PROVIDER_FATAL"] = "PROVIDER_FATAL";
58
+ ErrorCode$1["FLAG_NOT_FOUND"] = "FLAG_NOT_FOUND";
59
+ ErrorCode$1["TYPE_MISMATCH"] = "TYPE_MISMATCH";
60
+ ErrorCode$1["GENERAL"] = "GENERAL";
61
+ return ErrorCode$1;
62
+ }({});
63
+ function error(errorCode, errorMessage) {
64
+ return {
65
+ flags: {},
66
+ resolveId: "",
67
+ resolveToken: "",
68
+ errorCode,
69
+ errorMessage
70
+ };
71
+ }
72
+ function resolve(bundle, flagKey, defaultValue, logger$1) {
73
+ const [flagName, ...path] = flagKey.split(".");
74
+ const flag = bundle?.flags[flagName];
75
+ if (bundle?.errorCode) {
76
+ logger$1?.warn(`Flag evaluation for "%s" failed. %s %s`, flagKey, bundle.errorCode, bundle?.errorMessage);
77
+ return {
78
+ reason: "ERROR",
79
+ errorCode: bundle.errorCode,
80
+ errorMessage: bundle.errorMessage,
81
+ value: defaultValue,
82
+ shouldApply: false
83
+ };
84
+ }
85
+ if (!flag) {
86
+ logger$1?.warn(`Flag evaluation for '${flagKey}' failed: flag not found`);
87
+ return {
88
+ reason: "ERROR",
89
+ errorCode: ErrorCode.FLAG_NOT_FOUND,
90
+ value: defaultValue,
91
+ shouldApply: false
92
+ };
93
+ }
94
+ let value = flag.value;
95
+ for (let i = 0; i < path.length; i++) {
96
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {
97
+ reason: "ERROR",
98
+ value: defaultValue,
99
+ errorCode: ErrorCode.TYPE_MISMATCH,
100
+ errorMessage: `resolved value is not an object at ${[flagName, ...path.slice(0, i)].join(".")}`,
101
+ shouldApply: false
102
+ };
103
+ value = value[path[i]];
104
+ }
105
+ try {
106
+ const validated = evaluateAssignment(value, defaultValue, [flagName, ...path]);
107
+ return {
108
+ ...flag,
109
+ value: validated
110
+ };
111
+ } catch (e) {
112
+ return {
113
+ reason: "ERROR",
114
+ value: defaultValue,
115
+ errorCode: ErrorCode.TYPE_MISMATCH,
116
+ errorMessage: String(e),
117
+ shouldApply: false
118
+ };
119
+ }
120
+ }
121
+ function evaluateAssignment(resolvedValue, defaultValue, path) {
122
+ const resolvedType = typeof resolvedValue;
123
+ const defaultType = typeof defaultValue;
124
+ if (Array.isArray(defaultValue)) throw `arrays are not supported as flag values at ${path.join(".")}`;
125
+ if (defaultValue === null) return resolvedValue;
126
+ if (resolvedValue === null) return defaultValue;
127
+ if (resolvedType !== defaultType) throw `resolved value (${resolvedType}) isn't assignable to default type (${defaultType}) at ${path.join(".")}`;
128
+ if (typeof resolvedValue === "object") {
129
+ const result = { ...resolvedValue };
130
+ for (const [key, value] of Object.entries(defaultValue)) {
131
+ if (!hasKey(resolvedValue, key)) throw `resolved value is missing field "${key}" at ${path.join(".")}`;
132
+ result[key] = evaluateAssignment(resolvedValue[key], value, [...path, key]);
133
+ }
134
+ return result;
135
+ }
136
+ return resolvedValue;
137
+ }
138
+ const ConfidenceContext = createContext(null);
139
+ const warnedFlags = /* @__PURE__ */ new Set();
140
+ function ConfidenceClientProvider({ bundle, apply, children }) {
141
+ const appliedFlags = useRef(/* @__PURE__ */ new Set());
142
+ const filteredApply = useCallback((flagName) => {
143
+ if (appliedFlags.current.has(flagName)) return Promise.resolve();
144
+ appliedFlags.current.add(flagName);
145
+ return apply(flagName);
146
+ }, [apply]);
147
+ return /* @__PURE__ */ jsx(ConfidenceContext.Provider, {
148
+ value: {
149
+ bundle,
150
+ apply: filteredApply
151
+ },
152
+ children
153
+ });
154
+ }
155
+ function useFlag(flagKey, defaultValue) {
156
+ return useFlagDetails(flagKey, defaultValue).value;
157
+ }
158
+ function useFlagDetails(flagKey, defaultValue, options) {
159
+ const ctx = useContext(ConfidenceContext);
160
+ if (!ctx && !warnedFlags.has(flagKey)) {
161
+ warnedFlags.add(flagKey);
162
+ devWarn(`[Confidence] useFlagDetails("${flagKey}") called without a parent ConfidenceProvider. Returning default value.`);
163
+ }
164
+ const bundle = ctx?.bundle ?? error(ErrorCode.GENERAL, "useFlagDetails called without a parent ConfidenceProvider");
165
+ const [baseFlagName] = flagKey.split(".", 1);
166
+ const resolution = resolve(bundle, flagKey, defaultValue);
167
+ const autoExpose = options?.expose !== false;
168
+ const doExpose = useCallback(() => {
169
+ if (resolution.shouldApply) ctx?.apply(baseFlagName);
170
+ }, [
171
+ ctx,
172
+ baseFlagName,
173
+ resolution.shouldApply
174
+ ]);
175
+ useEffect(() => {
176
+ if (autoExpose) doExpose();
177
+ }, [autoExpose, doExpose]);
178
+ const expose = useCallback(() => {
179
+ if (autoExpose) devWarn(`[Confidence] expose() called on "${flagKey}" but auto-exposure is enabled. Call is ignored.`);
180
+ else doExpose();
181
+ }, [
182
+ autoExpose,
183
+ doExpose,
184
+ flagKey
185
+ ]);
186
+ return {
187
+ flagKey,
188
+ flagMetadata: {},
189
+ ...resolution,
190
+ expose
191
+ };
192
+ }
193
+ export { ConfidenceClientProvider, useFlag, useFlagDetails };
Binary file