@spotify-confidence/openfeature-server-provider-local 0.7.0 → 0.8.1

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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.1](https://github.com/spotify/confidence-resolver/compare/openfeature-provider-js-v0.8.0...openfeature-provider-js-v0.8.1) (2026-02-10)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * rust-guest bumped from 0.1.14 to 0.1.15
11
+
12
+ ## [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)
13
+
14
+
15
+ ### Features
16
+
17
+ * **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))
18
+ * **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))
19
+
20
+
21
+ ### Dependencies
22
+
23
+ * The following workspace dependencies were updated
24
+ * dependencies
25
+ * rust-guest bumped from 0.1.13 to 0.1.14
26
+
3
27
  ## [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)
4
28
 
5
29
 
package/README.md CHANGED
@@ -8,6 +8,7 @@ OpenFeature provider for the Spotify Confidence resolver (local mode, powered by
8
8
  - Automatic state refresh and batched flag log flushing
9
9
  - Pluggable `fetch` with retries, timeouts and routing
10
10
  - Optional logging using `debug`
11
+ - **[React integration](./README-REACT.md)** for Next.js with Server Components
11
12
 
12
13
  ## Requirements
13
14
 
@@ -194,6 +195,20 @@ const provider = createConfidenceServerProvider({
194
195
  });
195
196
  ```
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
+
197
212
  ### A note on browser usage
198
213
 
199
214
  While browsers are mentioned in this doc, this package is intended for server-side use only. Two concerns for browser usage:
@@ -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,194 @@
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
+ for (const child of this.childLoggers.values()) child.configure(debug);
30
+ }
31
+ getLogger(name) {
32
+ let child = (this.childLoggers.get(name));
33
+ if (!child) {
34
+ child = new LoggerImpl(this.name + ":" + name);
35
+ this.childLoggers.set(name, child);
36
+ }
37
+ return child;
38
+ }
39
+ }("cnfd");
40
+ logger.getLogger.bind(logger);
41
+ async function loadDebug() {
42
+ try {
43
+ const { default: debug } = await import("debug");
44
+ if (typeof debug !== "function") return null;
45
+ return debug;
46
+ } catch (e) {
47
+ return null;
48
+ }
49
+ }
50
+ function devWarn(message) {
51
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") console.warn(message);
52
+ }
53
+ function hasKey(obj, key) {
54
+ return key in obj;
55
+ }
56
+ let ErrorCode = /* @__PURE__ */ function(ErrorCode$1) {
57
+ ErrorCode$1["PROVIDER_NOT_READY"] = "PROVIDER_NOT_READY";
58
+ ErrorCode$1["PROVIDER_FATAL"] = "PROVIDER_FATAL";
59
+ ErrorCode$1["FLAG_NOT_FOUND"] = "FLAG_NOT_FOUND";
60
+ ErrorCode$1["TYPE_MISMATCH"] = "TYPE_MISMATCH";
61
+ ErrorCode$1["GENERAL"] = "GENERAL";
62
+ return ErrorCode$1;
63
+ }({});
64
+ function error(errorCode, errorMessage) {
65
+ return {
66
+ flags: {},
67
+ resolveId: "",
68
+ resolveToken: "",
69
+ errorCode,
70
+ errorMessage
71
+ };
72
+ }
73
+ function resolve(bundle, flagKey, defaultValue, logger$1) {
74
+ const [flagName, ...path] = flagKey.split(".");
75
+ const flag = bundle?.flags[flagName];
76
+ if (bundle?.errorCode) {
77
+ logger$1?.warn(`Flag evaluation for "%s" failed. %s %s`, flagKey, bundle.errorCode, bundle?.errorMessage);
78
+ return {
79
+ reason: "ERROR",
80
+ errorCode: bundle.errorCode,
81
+ errorMessage: bundle.errorMessage,
82
+ value: defaultValue,
83
+ shouldApply: false
84
+ };
85
+ }
86
+ if (!flag) {
87
+ logger$1?.warn(`Flag evaluation for '${flagKey}' failed: flag not found`);
88
+ return {
89
+ reason: "ERROR",
90
+ errorCode: ErrorCode.FLAG_NOT_FOUND,
91
+ value: defaultValue,
92
+ shouldApply: false
93
+ };
94
+ }
95
+ let value = flag.value;
96
+ for (let i = 0; i < path.length; i++) {
97
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {
98
+ reason: "ERROR",
99
+ value: defaultValue,
100
+ errorCode: ErrorCode.TYPE_MISMATCH,
101
+ errorMessage: `resolved value is not an object at ${[flagName, ...path.slice(0, i)].join(".")}`,
102
+ shouldApply: false
103
+ };
104
+ value = value[path[i]];
105
+ }
106
+ try {
107
+ const validated = evaluateAssignment(value, defaultValue, [flagName, ...path]);
108
+ return {
109
+ ...flag,
110
+ value: validated
111
+ };
112
+ } catch (e) {
113
+ return {
114
+ reason: "ERROR",
115
+ value: defaultValue,
116
+ errorCode: ErrorCode.TYPE_MISMATCH,
117
+ errorMessage: String(e),
118
+ shouldApply: false
119
+ };
120
+ }
121
+ }
122
+ function evaluateAssignment(resolvedValue, defaultValue, path) {
123
+ const resolvedType = typeof resolvedValue;
124
+ const defaultType = typeof defaultValue;
125
+ if (Array.isArray(defaultValue)) throw `arrays are not supported as flag values at ${path.join(".")}`;
126
+ if (defaultValue === null) return resolvedValue;
127
+ if (resolvedValue === null) return defaultValue;
128
+ if (resolvedType !== defaultType) throw `resolved value (${resolvedType}) isn't assignable to default type (${defaultType}) at ${path.join(".")}`;
129
+ if (typeof resolvedValue === "object") {
130
+ const result = { ...resolvedValue };
131
+ for (const [key, value] of Object.entries(defaultValue)) {
132
+ if (!hasKey(resolvedValue, key)) throw `resolved value is missing field "${key}" at ${path.join(".")}`;
133
+ result[key] = evaluateAssignment(resolvedValue[key], value, [...path, key]);
134
+ }
135
+ return result;
136
+ }
137
+ return resolvedValue;
138
+ }
139
+ const ConfidenceContext = createContext(null);
140
+ const warnedFlags = /* @__PURE__ */ new Set();
141
+ function ConfidenceClientProvider({ bundle, apply, children }) {
142
+ const appliedFlags = useRef(/* @__PURE__ */ new Set());
143
+ const filteredApply = useCallback((flagName) => {
144
+ if (appliedFlags.current.has(flagName)) return Promise.resolve();
145
+ appliedFlags.current.add(flagName);
146
+ return apply(flagName);
147
+ }, [apply]);
148
+ return /* @__PURE__ */ jsx(ConfidenceContext.Provider, {
149
+ value: {
150
+ bundle,
151
+ apply: filteredApply
152
+ },
153
+ children
154
+ });
155
+ }
156
+ function useFlag(flagKey, defaultValue) {
157
+ return useFlagDetails(flagKey, defaultValue).value;
158
+ }
159
+ function useFlagDetails(flagKey, defaultValue, options) {
160
+ const ctx = useContext(ConfidenceContext);
161
+ if (!ctx && !warnedFlags.has(flagKey)) {
162
+ warnedFlags.add(flagKey);
163
+ devWarn(`[Confidence] useFlagDetails("${flagKey}") called without a parent ConfidenceProvider. Returning default value.`);
164
+ }
165
+ const bundle = ctx?.bundle ?? error(ErrorCode.GENERAL, "useFlagDetails called without a parent ConfidenceProvider");
166
+ const [baseFlagName] = flagKey.split(".", 1);
167
+ const resolution = resolve(bundle, flagKey, defaultValue);
168
+ const autoExpose = options?.expose !== false;
169
+ const doExpose = useCallback(() => {
170
+ if (resolution.shouldApply) ctx?.apply(baseFlagName);
171
+ }, [
172
+ ctx,
173
+ baseFlagName,
174
+ resolution.shouldApply
175
+ ]);
176
+ useEffect(() => {
177
+ if (autoExpose) doExpose();
178
+ }, [autoExpose, doExpose]);
179
+ const expose = useCallback(() => {
180
+ if (autoExpose) devWarn(`[Confidence] expose() called on "${flagKey}" but auto-exposure is enabled. Call is ignored.`);
181
+ else doExpose();
182
+ }, [
183
+ autoExpose,
184
+ doExpose,
185
+ flagKey
186
+ ]);
187
+ return {
188
+ flagKey,
189
+ flagMetadata: {},
190
+ ...resolution,
191
+ expose
192
+ };
193
+ }
194
+ export { ConfidenceClientProvider, useFlag, useFlagDetails };
Binary file
@@ -1,5 +1,5 @@
1
1
  import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
2
- import { EvaluationContext, JsonValue, Provider, ProviderMetadata, ProviderStatus, ResolutionDetails } from "@openfeature/server-sdk";
2
+ import { EvaluationContext, JsonValue, Provider, ProviderMetadata, ProviderStatus } from "@openfeature/server-sdk";
3
3
 
4
4
  //#region src/proto/confidence/flags/types/v1/types.d.ts
5
5
  /** Schema for the value of a flag. */
@@ -173,6 +173,27 @@ interface ResolveFlagsResponse {
173
173
  /** Unique identifier for this particular resolve request. */
174
174
  resolveId: string;
175
175
  }
176
+ interface ApplyFlagsRequest {
177
+ /** The flags to apply and information about when they were applied. */
178
+ flags: AppliedFlag[];
179
+ /** Credentials for the client. */
180
+ clientSecret: string;
181
+ /** An opaque token that was returned from `ResolveFlags`; it must be set. */
182
+ resolveToken: Uint8Array;
183
+ /**
184
+ * The client time when the this request was sent, used for correcting
185
+ * clock skew from the client.
186
+ */
187
+ sendTime?: Date | undefined;
188
+ /** Information about the SDK used to initiate the request. */
189
+ sdk?: Sdk | undefined;
190
+ }
191
+ interface AppliedFlag {
192
+ /** The id of the flag that should be applied, has the format `flags/*`. */
193
+ flag: string;
194
+ /** The client time when the flag was applied. */
195
+ applyTime?: Date | undefined;
196
+ }
176
197
  interface ResolvedFlag {
177
198
  /** The id of the flag that as resolved. */
178
199
  flag: string;
@@ -189,9 +210,13 @@ interface ResolvedFlag {
189
210
  flagSchema?: FlagSchema_StructFlagSchema | undefined;
190
211
  /** The reason to why the flag could be resolved or not. */
191
212
  reason: ResolveReason;
213
+ /** Determines whether the flag should be applied in the clients */
214
+ shouldApply: boolean;
192
215
  }
193
216
  declare const ResolveFlagsRequest: MessageFns$3<ResolveFlagsRequest>;
194
217
  declare const ResolveFlagsResponse: MessageFns$3<ResolveFlagsResponse>;
218
+ declare const ApplyFlagsRequest: MessageFns$3<ApplyFlagsRequest>;
219
+ declare const AppliedFlag: MessageFns$3<AppliedFlag>;
195
220
  declare const ResolvedFlag: MessageFns$3<ResolvedFlag>;
196
221
  type Builtin$3 = Date | Function | Uint8Array | string | number | boolean | undefined;
197
222
  type DeepPartial$3<T> = T extends Builtin$3 ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial$3<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial$3<U>> : T extends {} ? { [K in keyof T]?: DeepPartial$3<T[K]> } : Partial<T>;
@@ -322,6 +347,7 @@ interface LocalResolver {
322
347
  setResolverState(request: SetResolverStateRequest): void;
323
348
  flushLogs(): Uint8Array;
324
349
  flushAssigned(): Uint8Array;
350
+ applyFlags(request: ApplyFlagsRequest): void;
325
351
  }
326
352
  //#endregion
327
353
  //#region src/materialization.d.ts
@@ -398,7 +424,40 @@ interface MaterializationStore {
398
424
  writeMaterializations?(writeOps: MaterializationStore.WriteOp[]): Promise<void>;
399
425
  }
400
426
  //#endregion
427
+ //#region src/types.d.ts
428
+ type ResolutionReason = "ERROR" | "FLAG_ARCHIVED" | "MATCH" | "NO_SEGMENT_MATCH" | "TARGETING_KEY_ERROR" | "NO_TREATMENT_MATCH" | "UNSPECIFIED";
429
+ declare enum ErrorCode {
430
+ PROVIDER_NOT_READY = "PROVIDER_NOT_READY",
431
+ PROVIDER_FATAL = "PROVIDER_FATAL",
432
+ FLAG_NOT_FOUND = "FLAG_NOT_FOUND",
433
+ TYPE_MISMATCH = "TYPE_MISMATCH",
434
+ GENERAL = "GENERAL",
435
+ }
436
+ interface ResolutionDetails<T> {
437
+ reason: ResolutionReason;
438
+ value: T;
439
+ variant?: string;
440
+ errorCode?: ErrorCode;
441
+ errorMessage?: string;
442
+ shouldApply: boolean;
443
+ }
444
+ type FlagPrimitive = null | boolean | string | number;
445
+ type FlagObject = {
446
+ [key: string]: FlagValue;
447
+ };
448
+ type FlagValue = FlagPrimitive | FlagObject;
449
+ //#endregion
450
+ //#region src/flag-bundle.d.ts
451
+ interface FlagBundle {
452
+ flags: Record<string, ResolutionDetails<FlagObject | null> | undefined>;
453
+ resolveId: string;
454
+ resolveToken: string;
455
+ errorCode?: ErrorCode;
456
+ errorMessage?: string;
457
+ }
458
+ //#endregion
401
459
  //#region src/ConfidenceServerProviderLocal.d.ts
460
+ type FlagBundle$1 = FlagBundle;
402
461
  interface ProviderOptions {
403
462
  flagClientSecret: string;
404
463
  initializeTimeout?: number;
@@ -430,19 +489,15 @@ declare class ConfidenceServerProviderLocal implements Provider {
430
489
  constructor(resolverOrPromise: LocalResolver | Promise<LocalResolver>, options: ProviderOptions);
431
490
  initialize(context?: EvaluationContext): Promise<void>;
432
491
  onClose(): Promise<void>;
433
- evaluate<T>(flagKey: string, defaultValue: T, context: EvaluationContext): Promise<ResolutionDetails<T>>;
492
+ resolve(context: EvaluationContext, flagNames: string[], apply?: boolean): Promise<FlagBundle$1>;
493
+ evaluate<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext): Promise<ResolutionDetails<T>>;
434
494
  private resolveWithSticky;
435
- /**
436
- * Extract and validate the value from a resolved flag.
437
- */
438
- private extractValue;
439
495
  updateState(signal?: AbortSignal): Promise<void>;
440
496
  flush(signal?: AbortSignal): Promise<void>;
441
497
  private flushAssigned;
442
498
  private sendFlagLogs;
443
499
  private readMaterializations;
444
500
  private writeMaterializations;
445
- private static convertReason;
446
501
  private static convertEvaluationContext;
447
502
  /** Resolves with an evaluation of a Boolean flag */
448
503
  resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, context: EvaluationContext): Promise<ResolutionDetails<boolean>>;
@@ -452,6 +507,13 @@ declare class ConfidenceServerProviderLocal implements Provider {
452
507
  resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext): Promise<ResolutionDetails<T>>;
453
508
  /** Resolves with an evaluation of a String flag */
454
509
  resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext): Promise<ResolutionDetails<string>>;
510
+ /**
511
+ * Applies a previously resolved flag, logging that it was used/exposed.
512
+ * Call this when a flag value is actually rendered or used in the client.
513
+ * @param resolveToken - Base64-encoded resolve token from the flag bundle
514
+ * @param flagName - Name of the flag to apply
515
+ */
516
+ applyFlag(resolveToken: string, flagName: string): void;
455
517
  }
456
518
  //#endregion
457
519
  //#region src/index.fetch.d.ts