@featureflare/react 0.0.24 → 0.0.26

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/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { FeatureFlareUserPayload, FeatureFlareClient } from '@featureflare/sdk-js';
3
+ import { FeatureFlareBootstrapPayload, FeatureFlarePersistentCacheAdapter, FeatureFlareMetricName, FeatureFlareMetricTags, FeatureFlareUserPayload, FeatureFlareClient, FeatureFlareEvaluationMetadata } from '@featureflare/sdk-js';
4
4
  export { FeatureFlareUserPayload } from '@featureflare/sdk-js';
5
5
 
6
6
  type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';
@@ -12,6 +12,20 @@ type FeatureFlareReactConfig = {
12
12
  /** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
13
13
  projectKey?: string;
14
14
  envKey?: FeatureFlareEnvironmentKey | string;
15
+ timeoutMs?: number;
16
+ maxRetries?: number;
17
+ backoffMs?: number;
18
+ jitter?: number;
19
+ cacheTtlMs?: number;
20
+ staleTtlMs?: number;
21
+ bootstrap?: FeatureFlareBootstrapPayload;
22
+ persistentCache?: FeatureFlarePersistentCacheAdapter;
23
+ onMetric?: (metricName: FeatureFlareMetricName, value: number, tags?: FeatureFlareMetricTags) => void;
24
+ realtime?: {
25
+ enabled?: boolean;
26
+ pollingIntervalMs?: number;
27
+ ssePath?: string;
28
+ };
15
29
  };
16
30
  declare function resolveFeatureFlareBrowserConfig(input?: {
17
31
  envKey?: FeatureFlareEnvironmentKey;
@@ -21,11 +35,13 @@ type FeatureFlareContextValue = {
21
35
  client: FeatureFlareClient;
22
36
  user: FeatureFlareUserPayload;
23
37
  setUser: (next: FeatureFlareUserPayload) => void;
24
- getFlagsState: (defaultValue: boolean) => FlagsState$1;
38
+ getFlagsState: (defaultValue: boolean) => FlagsState;
25
39
  refreshFlags: (defaultValue: boolean) => void;
26
40
  subscribeFlags: (defaultValue: boolean, listener: () => void, options?: FlagsSubscriptionOptions) => () => void;
41
+ subscribeFlag: (flagKey: string, defaultValue: boolean, listener: () => void, options?: FlagsSubscriptionOptions) => () => void;
42
+ getFlagDiagnostics: (flagKey: string) => FeatureFlareEvaluationMetadata | null;
27
43
  };
28
- type FlagsState$1 = {
44
+ type FlagsState = {
29
45
  flags: Array<{
30
46
  key: string;
31
47
  value: boolean;
@@ -58,23 +74,27 @@ type BoolFlagsState = {
58
74
  loading: boolean;
59
75
  errors: Record<string, string>;
60
76
  };
61
- type FlagsState = {
62
- flags: Array<{
63
- key: string;
64
- value: boolean;
65
- }>;
66
- loading: boolean;
67
- error: string | null;
68
- };
69
77
  type UseFlagsOptions = FlagsSubscriptionOptions;
70
78
  type UseFlagsInput = UseFlagsOptions & {
71
79
  defaultValue?: boolean;
72
80
  user?: FeatureFlareUserPayload;
73
81
  };
82
+ type FlagDiagnostics = {
83
+ source: FeatureFlareEvaluationMetadata['source'] | 'unknown';
84
+ reason: FeatureFlareEvaluationMetadata['reason'] | 'unknown';
85
+ isStale: boolean;
86
+ updatedAt?: number;
87
+ staleAt?: number;
88
+ expiresAt?: number;
89
+ latencyMs?: number;
90
+ };
74
91
  declare function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void];
92
+ declare function useFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
75
93
  declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
76
- declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
94
+ declare function useFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
77
95
  declare function useFlags(input?: UseFlagsInput): FlagsState;
78
96
  declare function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;
97
+ declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
98
+ declare function useFlagDiagnostics(flagKey: string, defaultValue?: boolean): FlagDiagnostics;
79
99
 
80
- export { type FeatureFlareEnvironmentKey, FeatureFlareProvider, type FeatureFlareReactConfig, type FlagsState$1 as FlagsState, type FlagsSubscriptionOptions, resolveFeatureFlareBrowserConfig, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser, useFlags };
100
+ export { type FeatureFlareEnvironmentKey, FeatureFlareProvider, type FeatureFlareReactConfig, type FlagDiagnostics, type FlagsState, type FlagsSubscriptionOptions, resolveFeatureFlareBrowserConfig, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser, useFlag, useFlagDiagnostics, useFlags };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { FeatureFlareUserPayload, FeatureFlareClient } from '@featureflare/sdk-js';
3
+ import { FeatureFlareBootstrapPayload, FeatureFlarePersistentCacheAdapter, FeatureFlareMetricName, FeatureFlareMetricTags, FeatureFlareUserPayload, FeatureFlareClient, FeatureFlareEvaluationMetadata } from '@featureflare/sdk-js';
4
4
  export { FeatureFlareUserPayload } from '@featureflare/sdk-js';
5
5
 
6
6
  type FeatureFlareEnvironmentKey = 'development' | 'staging' | 'production';
@@ -12,6 +12,20 @@ type FeatureFlareReactConfig = {
12
12
  /** Legacy/insecure browser mode: uses /api/v1/eval (no sdkKey). */
13
13
  projectKey?: string;
14
14
  envKey?: FeatureFlareEnvironmentKey | string;
15
+ timeoutMs?: number;
16
+ maxRetries?: number;
17
+ backoffMs?: number;
18
+ jitter?: number;
19
+ cacheTtlMs?: number;
20
+ staleTtlMs?: number;
21
+ bootstrap?: FeatureFlareBootstrapPayload;
22
+ persistentCache?: FeatureFlarePersistentCacheAdapter;
23
+ onMetric?: (metricName: FeatureFlareMetricName, value: number, tags?: FeatureFlareMetricTags) => void;
24
+ realtime?: {
25
+ enabled?: boolean;
26
+ pollingIntervalMs?: number;
27
+ ssePath?: string;
28
+ };
15
29
  };
16
30
  declare function resolveFeatureFlareBrowserConfig(input?: {
17
31
  envKey?: FeatureFlareEnvironmentKey;
@@ -21,11 +35,13 @@ type FeatureFlareContextValue = {
21
35
  client: FeatureFlareClient;
22
36
  user: FeatureFlareUserPayload;
23
37
  setUser: (next: FeatureFlareUserPayload) => void;
24
- getFlagsState: (defaultValue: boolean) => FlagsState$1;
38
+ getFlagsState: (defaultValue: boolean) => FlagsState;
25
39
  refreshFlags: (defaultValue: boolean) => void;
26
40
  subscribeFlags: (defaultValue: boolean, listener: () => void, options?: FlagsSubscriptionOptions) => () => void;
41
+ subscribeFlag: (flagKey: string, defaultValue: boolean, listener: () => void, options?: FlagsSubscriptionOptions) => () => void;
42
+ getFlagDiagnostics: (flagKey: string) => FeatureFlareEvaluationMetadata | null;
27
43
  };
28
- type FlagsState$1 = {
44
+ type FlagsState = {
29
45
  flags: Array<{
30
46
  key: string;
31
47
  value: boolean;
@@ -58,23 +74,27 @@ type BoolFlagsState = {
58
74
  loading: boolean;
59
75
  errors: Record<string, string>;
60
76
  };
61
- type FlagsState = {
62
- flags: Array<{
63
- key: string;
64
- value: boolean;
65
- }>;
66
- loading: boolean;
67
- error: string | null;
68
- };
69
77
  type UseFlagsOptions = FlagsSubscriptionOptions;
70
78
  type UseFlagsInput = UseFlagsOptions & {
71
79
  defaultValue?: boolean;
72
80
  user?: FeatureFlareUserPayload;
73
81
  };
82
+ type FlagDiagnostics = {
83
+ source: FeatureFlareEvaluationMetadata['source'] | 'unknown';
84
+ reason: FeatureFlareEvaluationMetadata['reason'] | 'unknown';
85
+ isStale: boolean;
86
+ updatedAt?: number;
87
+ staleAt?: number;
88
+ expiresAt?: number;
89
+ latencyMs?: number;
90
+ };
74
91
  declare function useFeatureFlareUser(): [FeatureFlareUserPayload, (next: FeatureFlareUserPayload) => void];
92
+ declare function useFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
75
93
  declare function useBoolFlag(flagKey: string, defaultValue?: boolean): BoolFlagState;
76
- declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
94
+ declare function useFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
77
95
  declare function useFlags(input?: UseFlagsInput): FlagsState;
78
96
  declare function useFlags(defaultValue?: boolean, options?: UseFlagsOptions): FlagsState;
97
+ declare function useBoolFlags(flagKeys: string[], defaultValue?: boolean): BoolFlagsState;
98
+ declare function useFlagDiagnostics(flagKey: string, defaultValue?: boolean): FlagDiagnostics;
79
99
 
80
- export { type FeatureFlareEnvironmentKey, FeatureFlareProvider, type FeatureFlareReactConfig, type FlagsState$1 as FlagsState, type FlagsSubscriptionOptions, resolveFeatureFlareBrowserConfig, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser, useFlags };
100
+ export { type FeatureFlareEnvironmentKey, FeatureFlareProvider, type FeatureFlareReactConfig, type FlagDiagnostics, type FlagsState, type FlagsSubscriptionOptions, resolveFeatureFlareBrowserConfig, useBoolFlag, useBoolFlags, useFeatureFlareContext, useFeatureFlareUser, useFlag, useFlagDiagnostics, useFlags };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/provider.tsx
2
2
  import React, { createContext, useEffect, useMemo, useRef, useState } from "react";
3
- import { FeatureFlareClient } from "@featureflare/sdk-js";
3
+ import {
4
+ FeatureFlareClient
5
+ } from "@featureflare/sdk-js";
4
6
  import { jsx } from "react/jsx-runtime";
5
7
  function resolveFeatureFlareBrowserConfig(input) {
6
8
  const explicitEnv = input?.envKey;
@@ -28,6 +30,29 @@ function normalizeSubscriptionOptions(options) {
28
30
  enabled: options?.enabled ?? true
29
31
  };
30
32
  }
33
+ function flagsToMap(flags) {
34
+ const map = /* @__PURE__ */ new Map();
35
+ for (const flag of flags) {
36
+ map.set(flag.key, flag.value);
37
+ }
38
+ return map;
39
+ }
40
+ function diffFlagKeys(prev, next) {
41
+ const changed = /* @__PURE__ */ new Set();
42
+ const prevMap = flagsToMap(prev);
43
+ const nextMap = flagsToMap(next);
44
+ for (const [key, value] of prevMap.entries()) {
45
+ if (!nextMap.has(key) || nextMap.get(key) !== value) {
46
+ changed.add(key);
47
+ }
48
+ }
49
+ for (const [key, value] of nextMap.entries()) {
50
+ if (!prevMap.has(key) || prevMap.get(key) !== value) {
51
+ changed.add(key);
52
+ }
53
+ }
54
+ return changed;
55
+ }
31
56
  function createFlagsStore(client, getUser) {
32
57
  const entries = /* @__PURE__ */ new Map();
33
58
  let nextSubscriberId = 1;
@@ -36,10 +61,11 @@ function createFlagsStore(client, getUser) {
36
61
  const key = defaultValue ? "1" : "0";
37
62
  const existing = entries.get(key);
38
63
  if (existing) return existing;
64
+ const cached = client.getCachedFlags();
39
65
  const created = {
40
66
  defaultValue,
41
- snapshot: { flags: [], loading: true, error: null },
42
- listeners: /* @__PURE__ */ new Set(),
67
+ snapshot: { flags: cached.flags, loading: !cached.hasData, error: null },
68
+ listeners: /* @__PURE__ */ new Map(),
43
69
  subscribers: /* @__PURE__ */ new Map(),
44
70
  timer: null,
45
71
  inFlight: false
@@ -47,8 +73,12 @@ function createFlagsStore(client, getUser) {
47
73
  entries.set(key, created);
48
74
  return created;
49
75
  };
50
- const emit = (entry) => {
51
- for (const listener of entry.listeners) listener();
76
+ const emit = (entry, changedKeys = null) => {
77
+ for (const { listener, flagKey } of entry.listeners.values()) {
78
+ if (!flagKey || changedKeys === null || changedKeys.has(flagKey)) {
79
+ listener();
80
+ }
81
+ }
52
82
  };
53
83
  const getEffectiveOptions = (entry) => {
54
84
  const active = [...entry.subscribers.values()].filter((s) => s.enabled);
@@ -94,13 +124,17 @@ function createFlagsStore(client, getUser) {
94
124
  if (entry.inFlight) return;
95
125
  entry.inFlight = true;
96
126
  try {
127
+ const previousFlags = entry.snapshot.flags;
97
128
  const flags = await client.flags(getUser(), defaultValue);
129
+ const changed = diffFlagKeys(previousFlags, flags);
98
130
  entry.snapshot = { flags, loading: false, error: null };
99
- emit(entry);
131
+ if (changed.size > 0 || previousFlags.length === 0) {
132
+ emit(entry, changed);
133
+ }
100
134
  } catch (error) {
101
135
  const message = error instanceof Error ? error.message : String(error);
102
136
  entry.snapshot = { ...entry.snapshot, loading: false, error: message };
103
- emit(entry);
137
+ emit(entry, null);
104
138
  } finally {
105
139
  entry.inFlight = false;
106
140
  schedule(entry);
@@ -109,14 +143,14 @@ function createFlagsStore(client, getUser) {
109
143
  const refreshNow = (defaultValue) => {
110
144
  const entry = getEntry(defaultValue);
111
145
  entry.snapshot = { ...entry.snapshot, loading: true, error: null };
112
- emit(entry);
146
+ emit(entry, null);
113
147
  void refresh(defaultValue, true);
114
148
  };
115
- const subscribe = (defaultValue, listener, options) => {
149
+ const subscribe = (defaultValue, listener, options, flagKey) => {
116
150
  const entry = getEntry(defaultValue);
117
151
  const subscriberId = nextSubscriberId;
118
152
  nextSubscriberId += 1;
119
- entry.listeners.add(listener);
153
+ entry.listeners.set(subscriberId, { listener, flagKey });
120
154
  entry.subscribers.set(subscriberId, normalizeSubscriptionOptions(options));
121
155
  const effective = getEffectiveOptions(entry);
122
156
  if (effective.enabled && !entry.inFlight && entry.snapshot.loading) {
@@ -125,7 +159,7 @@ function createFlagsStore(client, getUser) {
125
159
  schedule(entry);
126
160
  }
127
161
  return () => {
128
- entry.listeners.delete(listener);
162
+ entry.listeners.delete(subscriberId);
129
163
  entry.subscribers.delete(subscriberId);
130
164
  schedule(entry);
131
165
  };
@@ -133,7 +167,7 @@ function createFlagsStore(client, getUser) {
133
167
  const updateUser = () => {
134
168
  for (const entry of entries.values()) {
135
169
  entry.snapshot = { ...entry.snapshot, loading: true, error: null };
136
- emit(entry);
170
+ emit(entry, null);
137
171
  void refresh(entry.defaultValue);
138
172
  }
139
173
  };
@@ -145,10 +179,28 @@ function createFlagsStore(client, getUser) {
145
179
  void refresh(entry.defaultValue);
146
180
  }
147
181
  };
182
+ const unsubscribeClientUpdate = typeof client.on === "function" ? client.on(
183
+ "update",
184
+ ({ changedKeys }) => {
185
+ const changedSet = new Set(changedKeys);
186
+ for (const entry of entries.values()) {
187
+ const previous = entry.snapshot.flags;
188
+ const next = client.getCachedFlags().flags;
189
+ const diff = diffFlagKeys(previous, next);
190
+ if (diff.size === 0) continue;
191
+ const intersects = [...diff].some((key) => changedSet.has(key));
192
+ if (!intersects && changedSet.size > 0) continue;
193
+ entry.snapshot = { ...entry.snapshot, flags: next, loading: false, error: null };
194
+ emit(entry, diff);
195
+ }
196
+ }
197
+ ) : () => {
198
+ };
148
199
  if (typeof document !== "undefined") {
149
200
  document.addEventListener("visibilitychange", handleVisibilityChange);
150
201
  }
151
202
  const dispose = () => {
203
+ unsubscribeClientUpdate();
152
204
  if (typeof document !== "undefined") {
153
205
  document.removeEventListener("visibilitychange", handleVisibilityChange);
154
206
  }
@@ -167,7 +219,12 @@ function createFlagsStore(client, getUser) {
167
219
  return getEntry(defaultValue).snapshot;
168
220
  },
169
221
  refreshNow,
170
- subscribe,
222
+ subscribeAll(defaultValue, listener, options) {
223
+ return subscribe(defaultValue, listener, options);
224
+ },
225
+ subscribeFlag(flagKey, defaultValue, listener, options) {
226
+ return subscribe(defaultValue, listener, options, flagKey);
227
+ },
171
228
  updateUser,
172
229
  dispose
173
230
  };
@@ -186,13 +243,33 @@ function FeatureFlareProvider(props) {
186
243
  apiBaseUrl: props.config.apiBaseUrl,
187
244
  sdkKey: props.config.sdkKey,
188
245
  projectKey: props.config.projectKey,
189
- envKey: props.config.envKey
246
+ envKey: props.config.envKey,
247
+ timeoutMs: props.config.timeoutMs,
248
+ maxRetries: props.config.maxRetries,
249
+ backoffMs: props.config.backoffMs,
250
+ jitter: props.config.jitter,
251
+ cacheTtlMs: props.config.cacheTtlMs,
252
+ staleTtlMs: props.config.staleTtlMs,
253
+ bootstrap: props.config.bootstrap,
254
+ persistentCache: props.config.persistentCache,
255
+ onMetric: props.config.onMetric,
256
+ realtime: props.config.realtime
190
257
  });
191
258
  }, [
192
259
  props.config.apiBaseUrl,
260
+ props.config.backoffMs,
261
+ props.config.bootstrap,
262
+ props.config.cacheTtlMs,
193
263
  props.config.envKey,
264
+ props.config.jitter,
265
+ props.config.maxRetries,
266
+ props.config.onMetric,
267
+ props.config.persistentCache,
194
268
  props.config.projectKey,
195
- props.config.sdkKey
269
+ props.config.realtime,
270
+ props.config.sdkKey,
271
+ props.config.staleTtlMs,
272
+ props.config.timeoutMs
196
273
  ]);
197
274
  const flagsStore = useMemo(() => createFlagsStore(client, () => userRef.current), [client]);
198
275
  useEffect(() => {
@@ -202,8 +279,9 @@ function FeatureFlareProvider(props) {
202
279
  useEffect(() => {
203
280
  return () => {
204
281
  flagsStore.dispose();
282
+ client.dispose();
205
283
  };
206
- }, [flagsStore]);
284
+ }, [client, flagsStore]);
207
285
  const value = useMemo(
208
286
  () => ({
209
287
  client,
@@ -211,7 +289,9 @@ function FeatureFlareProvider(props) {
211
289
  setUser,
212
290
  getFlagsState: flagsStore.getState,
213
291
  refreshFlags: flagsStore.refreshNow,
214
- subscribeFlags: flagsStore.subscribe
292
+ subscribeFlags: flagsStore.subscribeAll,
293
+ subscribeFlag: (flagKey, defaultValue, listener, options) => flagsStore.subscribeFlag(flagKey, defaultValue, listener, options),
294
+ getFlagDiagnostics: (flagKey) => client.getFlagDiagnostics(flagKey)
215
295
  }),
216
296
  [client, flagsStore, setUser, user]
217
297
  );
@@ -224,7 +304,7 @@ function useFeatureFlareContext() {
224
304
  }
225
305
 
226
306
  // src/hooks.ts
227
- import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
307
+ import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useSyncExternalStore } from "react";
228
308
  var EMPTY_FLAGS_STATE = { flags: [], loading: true, error: null };
229
309
  function userFingerprint(user) {
230
310
  if (!user) return "";
@@ -235,83 +315,75 @@ function userFingerprint(user) {
235
315
  meta: user.meta ?? {}
236
316
  });
237
317
  }
318
+ function mapFlags(flags) {
319
+ const values = {};
320
+ for (const flag of flags) {
321
+ values[flag.key] = flag.value;
322
+ }
323
+ return values;
324
+ }
238
325
  function useFeatureFlareUser() {
239
326
  const { user, setUser } = useFeatureFlareContext();
240
327
  return [user, setUser];
241
328
  }
242
- function useBoolFlag(flagKey, defaultValue = false) {
243
- const { client, user } = useFeatureFlareContext();
244
- const [state, setState] = useState2({ value: defaultValue, loading: true, error: null });
245
- const userId = user.id ?? user.key ?? "";
246
- const key = useMemo2(() => `${flagKey}:${userId}`, [flagKey, userId]);
247
- const lastKey = useRef2("");
329
+ function useFlag(flagKey, defaultValue = false) {
330
+ const { subscribeFlag, getFlagsState, refreshFlags } = useFeatureFlareContext();
248
331
  useEffect2(() => {
249
- let cancelled = false;
250
- const nextKey = key;
251
- lastKey.current = nextKey;
252
- setState((s) => ({ ...s, loading: true, error: null }));
253
- (async () => {
254
- try {
255
- const v = await client.bool(flagKey, user, defaultValue);
256
- if (cancelled) return;
257
- if (lastKey.current !== nextKey) return;
258
- setState({ value: v, loading: false, error: null });
259
- } catch (e) {
260
- if (cancelled) return;
261
- if (lastKey.current !== nextKey) return;
262
- const msg = e instanceof Error ? e.message : String(e);
263
- setState({ value: defaultValue, loading: false, error: msg });
264
- }
265
- })();
266
- return () => {
267
- cancelled = true;
268
- };
269
- }, [client, defaultValue, flagKey, key, user]);
270
- return state;
332
+ refreshFlags(defaultValue);
333
+ }, [defaultValue, refreshFlags]);
334
+ const subscribe = useMemo2(
335
+ () => (onStoreChange) => subscribeFlag(flagKey, defaultValue, onStoreChange),
336
+ [defaultValue, flagKey, subscribeFlag]
337
+ );
338
+ const state = useSyncExternalStore(subscribe, () => getFlagsState(defaultValue), () => EMPTY_FLAGS_STATE);
339
+ const value = state.flags.find((entry) => entry.key === flagKey)?.value ?? defaultValue;
340
+ return {
341
+ value,
342
+ loading: state.loading,
343
+ error: state.error
344
+ };
271
345
  }
272
- function useBoolFlags(flagKeys, defaultValue = false) {
273
- const { client, user } = useFeatureFlareContext();
274
- const sortedKeys = useMemo2(() => [...flagKeys].map((k) => k.trim()).filter(Boolean).sort(), [flagKeys]);
275
- const userId = user.id ?? user.key ?? "";
276
- const key = useMemo2(() => `${sortedKeys.join(",")}:${userId}`, [sortedKeys, userId]);
277
- const [state, setState] = useState2({ values: {}, loading: true, errors: {} });
278
- const lastKey = useRef2("");
279
- useEffect2(() => {
280
- let cancelled = false;
281
- const nextKey = key;
282
- lastKey.current = nextKey;
283
- setState({ values: {}, loading: true, errors: {} });
284
- (async () => {
285
- const values = {};
286
- const errors = {};
287
- await Promise.all(
288
- sortedKeys.map(async (flagKey) => {
289
- try {
290
- values[flagKey] = await client.bool(flagKey, user, defaultValue);
291
- } catch (e) {
292
- values[flagKey] = defaultValue;
293
- errors[flagKey] = e instanceof Error ? e.message : String(e);
294
- }
295
- })
296
- );
297
- if (cancelled) return;
298
- if (lastKey.current !== nextKey) return;
299
- setState({ values, loading: false, errors });
300
- })();
301
- return () => {
302
- cancelled = true;
303
- };
304
- }, [client, defaultValue, key, sortedKeys, user]);
305
- return state;
346
+ function useBoolFlag(flagKey, defaultValue = false) {
347
+ return useFlag(flagKey, defaultValue);
306
348
  }
307
- function useFlags(defaultValueOrInput = false, options = {}) {
308
- const { subscribeFlags, getFlagsState, refreshFlags, setUser } = useFeatureFlareContext();
349
+ function useFlags(defaultValueOrInputOrKeys = false, optionsOrDefaultValue = {}) {
350
+ const { subscribeFlags, subscribeFlag, getFlagsState, refreshFlags, setUser } = useFeatureFlareContext();
351
+ if (Array.isArray(defaultValueOrInputOrKeys)) {
352
+ const keys = [...defaultValueOrInputOrKeys].map((key) => key.trim()).filter(Boolean);
353
+ const defaultValue2 = typeof optionsOrDefaultValue === "boolean" ? optionsOrDefaultValue : false;
354
+ useEffect2(() => {
355
+ refreshFlags(defaultValue2);
356
+ }, [defaultValue2, refreshFlags]);
357
+ const subscribe2 = useMemo2(
358
+ () => (onStoreChange) => {
359
+ const unsubs = keys.map((key) => subscribeFlag(key, defaultValue2, onStoreChange));
360
+ return () => {
361
+ for (const unsub of unsubs) unsub();
362
+ };
363
+ },
364
+ [defaultValue2, keys, subscribeFlag]
365
+ );
366
+ const state = useSyncExternalStore(subscribe2, () => getFlagsState(defaultValue2), () => EMPTY_FLAGS_STATE);
367
+ const values = mapFlags(state.flags);
368
+ const filtered = {};
369
+ for (const key of keys) {
370
+ filtered[key] = values[key] ?? defaultValue2;
371
+ }
372
+ return {
373
+ values: filtered,
374
+ loading: state.loading,
375
+ errors: state.error ? { __global: state.error } : {}
376
+ };
377
+ }
309
378
  const parsed = useMemo2(() => {
310
- if (typeof defaultValueOrInput === "boolean") {
311
- return { ...options, defaultValue: defaultValueOrInput };
379
+ if (typeof defaultValueOrInputOrKeys === "boolean") {
380
+ return {
381
+ ...typeof optionsOrDefaultValue === "object" && optionsOrDefaultValue !== null ? optionsOrDefaultValue : {},
382
+ defaultValue: defaultValueOrInputOrKeys
383
+ };
312
384
  }
313
- return defaultValueOrInput ?? {};
314
- }, [defaultValueOrInput, options]);
385
+ return defaultValueOrInputOrKeys ?? {};
386
+ }, [defaultValueOrInputOrKeys, optionsOrDefaultValue]);
315
387
  const defaultValue = parsed.defaultValue ?? false;
316
388
  const normalizedOptions = useMemo2(
317
389
  () => ({
@@ -340,6 +412,30 @@ function useFlags(defaultValueOrInput = false, options = {}) {
340
412
  );
341
413
  return useSyncExternalStore(subscribe, () => getFlagsState(defaultValue), () => EMPTY_FLAGS_STATE);
342
414
  }
415
+ function useBoolFlags(flagKeys, defaultValue = false) {
416
+ return useFlags(flagKeys, defaultValue);
417
+ }
418
+ function useFlagDiagnostics(flagKey, defaultValue = false) {
419
+ const { getFlagDiagnostics, subscribeFlag } = useFeatureFlareContext();
420
+ const subscribe = useMemo2(
421
+ () => (onStoreChange) => subscribeFlag(flagKey, defaultValue, onStoreChange),
422
+ [defaultValue, flagKey, subscribeFlag]
423
+ );
424
+ const metadata = useSyncExternalStore(
425
+ subscribe,
426
+ () => getFlagDiagnostics(flagKey),
427
+ () => null
428
+ );
429
+ return {
430
+ source: metadata?.source ?? "unknown",
431
+ reason: metadata?.reason ?? "unknown",
432
+ isStale: metadata?.isStale ?? false,
433
+ updatedAt: metadata?.updatedAt,
434
+ staleAt: metadata?.staleAt,
435
+ expiresAt: metadata?.expiresAt,
436
+ latencyMs: metadata?.latencyMs
437
+ };
438
+ }
343
439
  export {
344
440
  FeatureFlareProvider,
345
441
  resolveFeatureFlareBrowserConfig,
@@ -347,6 +443,8 @@ export {
347
443
  useBoolFlags,
348
444
  useFeatureFlareContext,
349
445
  useFeatureFlareUser,
446
+ useFlag,
447
+ useFlagDiagnostics,
350
448
  useFlags
351
449
  };
352
450
  //# sourceMappingURL=index.js.map