@getflaggy/sdk 0.2.4 → 0.3.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/dist/react.cjs +18 -10
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +19 -11
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.cjs
CHANGED
|
@@ -322,7 +322,6 @@ function FlaggyProvider({
|
|
|
322
322
|
const clientRef = (0, import_react2.useRef)(null);
|
|
323
323
|
const [ready, setReady] = (0, import_react2.useState)(false);
|
|
324
324
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
325
|
-
const [version, setVersion] = (0, import_react2.useState)(0);
|
|
326
325
|
(0, import_react2.useEffect)(() => {
|
|
327
326
|
const client = new FlaggyClient({
|
|
328
327
|
serverUrl,
|
|
@@ -339,14 +338,10 @@ function FlaggyProvider({
|
|
|
339
338
|
if (!client.ready) setError(err);
|
|
340
339
|
onError?.(err);
|
|
341
340
|
});
|
|
342
|
-
const unsubChange = client.on("change", () => {
|
|
343
|
-
setVersion((v) => v + 1);
|
|
344
|
-
});
|
|
345
341
|
client.initialize();
|
|
346
342
|
return () => {
|
|
347
343
|
unsubReady();
|
|
348
344
|
unsubError();
|
|
349
|
-
unsubChange();
|
|
350
345
|
client.destroy();
|
|
351
346
|
clientRef.current = null;
|
|
352
347
|
};
|
|
@@ -357,7 +352,6 @@ function FlaggyProvider({
|
|
|
357
352
|
clientRef.current.setContext(context);
|
|
358
353
|
}
|
|
359
354
|
}, [contextKey]);
|
|
360
|
-
void version;
|
|
361
355
|
if (!clientRef.current) return null;
|
|
362
356
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlaggyReactContext.Provider, { value: { client: clientRef.current, ready, error }, children });
|
|
363
357
|
}
|
|
@@ -366,10 +360,24 @@ function FlaggyProvider({
|
|
|
366
360
|
var import_react3 = require("react");
|
|
367
361
|
function useFlag(key, defaultValue) {
|
|
368
362
|
const ctx = (0, import_react3.useContext)(FlaggyReactContext);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
363
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
364
|
+
(onStoreChange) => {
|
|
365
|
+
if (!ctx) return () => {
|
|
366
|
+
};
|
|
367
|
+
const unsubChange = ctx.client.on("change", onStoreChange);
|
|
368
|
+
const unsubReady = ctx.client.on("ready", onStoreChange);
|
|
369
|
+
return () => {
|
|
370
|
+
unsubChange();
|
|
371
|
+
unsubReady();
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
[ctx]
|
|
375
|
+
);
|
|
376
|
+
const getSnapshot = (0, import_react3.useCallback)(
|
|
377
|
+
() => ctx ? ctx.client.getFlag(key, defaultValue) : defaultValue,
|
|
378
|
+
[ctx, key, defaultValue]
|
|
379
|
+
);
|
|
380
|
+
return (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, () => defaultValue);
|
|
373
381
|
}
|
|
374
382
|
|
|
375
383
|
// src/react/useFlaggy.ts
|
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/index.ts","../src/react/FlaggyProvider.tsx","../src/sse.ts","../src/client.ts","../src/react/context.ts","../src/react/useFlag.ts","../src/react/useFlaggy.ts"],"sourcesContent":["export { FlaggyProvider } from './FlaggyProvider';\nexport type { FlaggyProviderProps } from './FlaggyProvider';\nexport { useFlag } from './useFlag';\nexport { useFlaggy } from './useFlaggy';\nexport type { FlaggyContext as FlaggyEvalContext, FlagValue } from '../types';\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { FlaggyClient } from '../client';\nimport { FlaggyReactContext } from './context';\nimport type { FlaggyContext } from '../types';\n\nexport interface FlaggyProviderProps {\n serverUrl: string;\n apiKey: string;\n flags: string[];\n context?: FlaggyContext;\n enableStreaming?: boolean;\n /** Called when an error occurs (init failure, SSE error, etc.) */\n onError?: (error: Error) => void;\n children: ReactNode;\n}\n\nexport function FlaggyProvider({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n onError,\n children,\n}: FlaggyProviderProps) {\n const clientRef = useRef<FlaggyClient | null>(null);\n const [ready, setReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [version, setVersion] = useState(0);\n\n // Create and initialize client when serverUrl or apiKey change\n useEffect(() => {\n const client = new FlaggyClient({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n });\n clientRef.current = client;\n setReady(false);\n setError(null);\n\n const unsubReady = client.on('ready', () => setReady(true));\n const unsubError = client.on('error', (err) => {\n // Only set error state during init; SSE errors are transient\n if (!client.ready) setError(err);\n onError?.(err);\n });\n const unsubChange = client.on('change', () => {\n setVersion((v) => v + 1);\n });\n\n client.initialize();\n\n return () => {\n unsubReady();\n unsubError();\n unsubChange();\n client.destroy();\n clientRef.current = null;\n };\n }, [serverUrl, apiKey]);\n\n // Update context when it changes (deep comparison via JSON.stringify)\n const contextKey = context ? JSON.stringify(context) : '';\n useEffect(() => {\n if (clientRef.current && context && clientRef.current.ready) {\n clientRef.current.setContext(context);\n }\n }, [contextKey]);\n\n // version is used to force re-render when flags change via SSE\n void version;\n\n if (!clientRef.current) return null;\n\n return (\n <FlaggyReactContext.Provider value={{ client: clientRef.current, ready, error }}>\n {children}\n </FlaggyReactContext.Provider>\n );\n}\n","import type { SSEEvent } from './types';\n\nexport interface SSEManagerOptions {\n url: string;\n apiKey: string;\n onEvent: (event: SSEEvent) => void;\n onError: (error: Error) => void;\n retryDelay?: number;\n maxRetryDelay?: number;\n}\n\nexport class SSEManager {\n private abortController: AbortController | null = null;\n private retryCount = 0;\n private retryTimeout: ReturnType<typeof setTimeout> | null = null;\n private destroyed = false;\n\n private readonly url: string;\n private readonly apiKey: string;\n private readonly onEvent: (event: SSEEvent) => void;\n private readonly onError: (error: Error) => void;\n private readonly retryDelay: number;\n private readonly maxRetryDelay: number;\n\n constructor(options: SSEManagerOptions) {\n this.url = options.url;\n this.apiKey = options.apiKey;\n this.onEvent = options.onEvent;\n this.onError = options.onError;\n this.retryDelay = options.retryDelay ?? 1000;\n this.maxRetryDelay = options.maxRetryDelay ?? 30_000;\n }\n\n connect(): void {\n if (this.destroyed) return;\n\n this.abortController = new AbortController();\n\n fetch(this.url, {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: 'text/event-stream',\n },\n signal: this.abortController.signal,\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error(`SSE connection failed: ${response.status}`);\n }\n if (!response.body) {\n throw new Error('SSE response has no body');\n }\n\n this.retryCount = 0;\n this.readStream(response.body);\n })\n .catch((err: unknown) => {\n if (this.destroyed) return;\n if (this.isAbortError(err)) return;\n\n this.onError(err instanceof Error ? err : new Error(String(err)));\n this.reconnect();\n });\n }\n\n destroy(): void {\n this.destroyed = true;\n this.abortController?.abort();\n this.abortController = null;\n if (this.retryTimeout) {\n clearTimeout(this.retryTimeout);\n this.retryTimeout = null;\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentEvent = '';\n let currentData = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('event:')) {\n currentEvent = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n currentData = line.slice(5).trim();\n } else if (line === '') {\n // Empty line = end of event\n if (currentData) {\n this.handleEvent(currentEvent, currentData);\n }\n currentEvent = '';\n currentData = '';\n }\n }\n }\n } catch (err: unknown) {\n if (this.destroyed) return;\n if (this.isAbortError(err)) return;\n\n this.onError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n reader.releaseLock();\n }\n\n // Stream ended — reconnect if not destroyed\n if (!this.destroyed) {\n this.reconnect();\n }\n }\n\n private handleEvent(eventType: string, data: string): void {\n try {\n const parsed = JSON.parse(data) as Record<string, unknown>;\n // SSE \"event:\" field is the authoritative event type\n if (eventType) {\n parsed.type = eventType;\n }\n this.onEvent(parsed as SSEEvent);\n } catch {\n // Malformed event data, skip\n }\n }\n\n private reconnect(): void {\n if (this.destroyed) return;\n\n const delay = this.getBackoffDelay();\n this.retryCount++;\n this.retryTimeout = setTimeout(() => {\n this.retryTimeout = null;\n this.connect();\n }, delay);\n }\n\n /** Safari/WebKit throws TypeError instead of AbortError when a fetch is aborted */\n private isAbortError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === 'AbortError') return true;\n if (err.name === 'TypeError' && /load failed|cancelled/i.test(err.message)) return true;\n return false;\n }\n\n private getBackoffDelay(): number {\n const delay = this.retryDelay * Math.pow(2, this.retryCount);\n const jitter = delay * 0.25 * (Math.random() * 2 - 1);\n return Math.min(delay + jitter, this.maxRetryDelay);\n }\n}\n","import { SSEManager } from './sse';\nimport type {\n FlagValue,\n FlaggyContext,\n FlaggyClientOptions,\n BatchEvaluateResponse,\n SSEEvent,\n FlagChangeListener,\n ReadyListener,\n ErrorListener,\n} from './types';\n\ntype EventMap = {\n change: FlagChangeListener;\n ready: ReadyListener;\n error: ErrorListener;\n};\n\nexport class FlaggyClient {\n private readonly serverUrl: string;\n private readonly apiKey: string;\n private readonly flags: string[];\n private readonly enableStreaming: boolean;\n private readonly sseRetryDelay: number;\n private readonly sseMaxRetryDelay: number;\n\n private context: FlaggyContext;\n private cache = new Map<string, FlagValue>();\n private _ready = false;\n private _error: Error | null = null;\n private sseManager: SSEManager | null = null;\n private contextAbortController: AbortController | null = null;\n\n private listeners: {\n change: Set<FlagChangeListener>;\n ready: Set<ReadyListener>;\n error: Set<ErrorListener>;\n } = {\n change: new Set(),\n ready: new Set(),\n error: new Set(),\n };\n\n constructor(options: FlaggyClientOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.flags = options.flags;\n this.context = options.context ?? {};\n this.enableStreaming = options.enableStreaming ?? true;\n this.sseRetryDelay = options.sseRetryDelay ?? 1000;\n this.sseMaxRetryDelay = options.sseMaxRetryDelay ?? 30_000;\n }\n\n get ready(): boolean {\n return this._ready;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n async initialize(): Promise<void> {\n try {\n const response = await this.fetchApi<BatchEvaluateResponse>(\n '/api/v1/evaluate/batch',\n { flags: this.flags, context: this.context },\n );\n this.applyBatchResult(response);\n this._ready = true;\n this.emit('ready');\n } catch (err: unknown) {\n this._error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', this._error);\n }\n\n if (this.enableStreaming) {\n this.startSSE();\n }\n }\n\n getFlag<T extends FlagValue>(key: string, defaultValue: T): T {\n if (!this._ready || !this.cache.has(key)) {\n return defaultValue;\n }\n return this.cache.get(key) as T;\n }\n\n async setContext(context: FlaggyContext): Promise<void> {\n this.context = context;\n this.contextAbortController?.abort();\n const controller = new AbortController();\n this.contextAbortController = controller;\n\n try {\n const response = await this.fetchApi<BatchEvaluateResponse>(\n '/api/v1/evaluate/batch',\n { flags: this.flags, context },\n controller.signal,\n );\n if (controller.signal.aborted) return;\n this.applyBatchResult(response);\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') return;\n this._error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', this._error);\n }\n }\n\n on<E extends keyof EventMap>(event: E, listener: EventMap[E]): () => void {\n (this.listeners[event] as Set<EventMap[E]>).add(listener);\n return () => {\n (this.listeners[event] as Set<EventMap[E]>).delete(listener);\n };\n }\n\n destroy(): void {\n this.sseManager?.destroy();\n this.sseManager = null;\n this.contextAbortController?.abort();\n this.contextAbortController = null;\n this.listeners.change.clear();\n this.listeners.ready.clear();\n this.listeners.error.clear();\n }\n\n private startSSE(): void {\n this.sseManager = new SSEManager({\n url: `${this.serverUrl}/api/v1/stream`,\n apiKey: this.apiKey,\n onEvent: (event) => this.handleSSEEvent(event),\n onError: (err) => this.emit('error', err),\n retryDelay: this.sseRetryDelay,\n maxRetryDelay: this.sseMaxRetryDelay,\n });\n this.sseManager.connect();\n }\n\n private async handleSSEEvent(event: SSEEvent): Promise<void> {\n // Ignore connection confirmation\n if (event.type === 'connected') return;\n\n // Any flag/rule/segment change — re-evaluate all flags via batch\n try {\n const response = await this.fetchApi<BatchEvaluateResponse>(\n '/api/v1/evaluate/batch',\n { flags: this.flags, context: this.context },\n );\n this.applyBatchResult(response);\n } catch {\n // Failed to re-evaluate, keep previous cached values\n }\n }\n\n private applyBatchResult(response: BatchEvaluateResponse): void {\n const newCache = new Map<string, FlagValue>();\n for (const flag of response.results) {\n newCache.set(flag.flag_key, flag.value);\n }\n\n // Emit changes for any values that differ\n for (const [key, newValue] of newCache) {\n const oldValue = this.cache.get(key);\n if (oldValue !== newValue) {\n this.emit('change', key, newValue);\n }\n }\n\n // Emit changes for keys that were removed\n for (const key of this.cache.keys()) {\n if (!newCache.has(key)) {\n this.emit('change', key, undefined as unknown as FlagValue);\n }\n }\n\n this.cache = newCache;\n }\n\n private emit(event: 'change', key: string, value: FlagValue): void;\n private emit(event: 'ready'): void;\n private emit(event: 'error', error: Error): void;\n private emit(event: keyof EventMap, ...args: unknown[]): void {\n if (event === 'change') {\n for (const listener of this.listeners.change) {\n listener(args[0] as string, args[1] as FlagValue);\n }\n } else if (event === 'ready') {\n for (const listener of this.listeners.ready) {\n listener();\n }\n } else if (event === 'error') {\n for (const listener of this.listeners.error) {\n listener(args[0] as Error);\n }\n }\n }\n\n private async fetchApi<T>(\n path: string,\n body: unknown,\n signal?: AbortSignal,\n ): Promise<T> {\n const response = await fetch(`${this.serverUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n signal,\n });\n if (!response.ok) {\n throw new Error(`Flaggy API error: ${response.status} ${response.statusText}`);\n }\n return response.json() as Promise<T>;\n }\n}\n","import { createContext } from 'react';\nimport type { FlaggyClient } from '../client';\n\nexport interface FlaggyContextValue {\n client: FlaggyClient;\n ready: boolean;\n error: Error | null;\n}\n\nexport const FlaggyReactContext = createContext<FlaggyContextValue | null>(null);\n","import { useContext } from 'react';\nimport { FlaggyReactContext } from './context';\nimport type { FlagValue } from '../types';\n\nexport function useFlag<T extends FlagValue>(key: string, defaultValue: T): T {\n const ctx = useContext(FlaggyReactContext);\n\n if (!ctx) {\n return defaultValue;\n }\n\n return ctx.client.getFlag(key, defaultValue);\n}\n","import { useContext } from 'react';\nimport { FlaggyReactContext } from './context';\n\nexport function useFlaggy() {\n const ctx = useContext(FlaggyReactContext);\n\n if (!ctx) {\n throw new Error('[flaggy] useFlaggy() must be used within a <FlaggyProvider>.');\n }\n\n return {\n client: ctx.client,\n ready: ctx.ready,\n error: ctx.error,\n };\n}\n"],"mappings":"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","names":["import_react","import_react","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/index.ts","../src/react/FlaggyProvider.tsx","../src/sse.ts","../src/client.ts","../src/react/context.ts","../src/react/useFlag.ts","../src/react/useFlaggy.ts"],"sourcesContent":["export { FlaggyProvider } from './FlaggyProvider';\nexport type { FlaggyProviderProps } from './FlaggyProvider';\nexport { useFlag } from './useFlag';\nexport { useFlaggy } from './useFlaggy';\nexport type { FlaggyContext as FlaggyEvalContext, FlagValue } from '../types';\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { FlaggyClient } from '../client';\nimport { FlaggyReactContext } from './context';\nimport type { FlaggyContext } from '../types';\n\nexport interface FlaggyProviderProps {\n serverUrl: string;\n apiKey: string;\n flags: string[];\n context?: FlaggyContext;\n enableStreaming?: boolean;\n /** Called when an error occurs (init failure, SSE error, etc.) */\n onError?: (error: Error) => void;\n children: ReactNode;\n}\n\nexport function FlaggyProvider({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n onError,\n children,\n}: FlaggyProviderProps) {\n const clientRef = useRef<FlaggyClient | null>(null);\n const [ready, setReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Create and initialize client when serverUrl or apiKey change\n useEffect(() => {\n const client = new FlaggyClient({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n });\n clientRef.current = client;\n setReady(false);\n setError(null);\n\n const unsubReady = client.on('ready', () => setReady(true));\n const unsubError = client.on('error', (err) => {\n // Only set error state during init; SSE errors are transient\n if (!client.ready) setError(err);\n onError?.(err);\n });\n\n client.initialize();\n\n return () => {\n unsubReady();\n unsubError();\n client.destroy();\n clientRef.current = null;\n };\n }, [serverUrl, apiKey]);\n\n // Update context when it changes (deep comparison via JSON.stringify)\n const contextKey = context ? JSON.stringify(context) : '';\n useEffect(() => {\n if (clientRef.current && context && clientRef.current.ready) {\n clientRef.current.setContext(context);\n }\n }, [contextKey]);\n\n if (!clientRef.current) return null;\n\n return (\n <FlaggyReactContext.Provider value={{ client: clientRef.current, ready, error }}>\n {children}\n </FlaggyReactContext.Provider>\n );\n}\n","import type { SSEEvent } from './types';\n\nexport interface SSEManagerOptions {\n url: string;\n apiKey: string;\n onEvent: (event: SSEEvent) => void;\n onError: (error: Error) => void;\n retryDelay?: number;\n maxRetryDelay?: number;\n}\n\nexport class SSEManager {\n private abortController: AbortController | null = null;\n private retryCount = 0;\n private retryTimeout: ReturnType<typeof setTimeout> | null = null;\n private destroyed = false;\n\n private readonly url: string;\n private readonly apiKey: string;\n private readonly onEvent: (event: SSEEvent) => void;\n private readonly onError: (error: Error) => void;\n private readonly retryDelay: number;\n private readonly maxRetryDelay: number;\n\n constructor(options: SSEManagerOptions) {\n this.url = options.url;\n this.apiKey = options.apiKey;\n this.onEvent = options.onEvent;\n this.onError = options.onError;\n this.retryDelay = options.retryDelay ?? 1000;\n this.maxRetryDelay = options.maxRetryDelay ?? 30_000;\n }\n\n connect(): void {\n if (this.destroyed) return;\n\n this.abortController = new AbortController();\n\n fetch(this.url, {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: 'text/event-stream',\n },\n signal: this.abortController.signal,\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error(`SSE connection failed: ${response.status}`);\n }\n if (!response.body) {\n throw new Error('SSE response has no body');\n }\n\n this.retryCount = 0;\n this.readStream(response.body);\n })\n .catch((err: unknown) => {\n if (this.destroyed) return;\n if (this.isAbortError(err)) return;\n\n this.onError(err instanceof Error ? err : new Error(String(err)));\n this.reconnect();\n });\n }\n\n destroy(): void {\n this.destroyed = true;\n this.abortController?.abort();\n this.abortController = null;\n if (this.retryTimeout) {\n clearTimeout(this.retryTimeout);\n this.retryTimeout = null;\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentEvent = '';\n let currentData = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('event:')) {\n currentEvent = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n currentData = line.slice(5).trim();\n } else if (line === '') {\n // Empty line = end of event\n if (currentData) {\n this.handleEvent(currentEvent, currentData);\n }\n currentEvent = '';\n currentData = '';\n }\n }\n }\n } catch (err: unknown) {\n if (this.destroyed) return;\n if (this.isAbortError(err)) return;\n\n this.onError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n reader.releaseLock();\n }\n\n // Stream ended — reconnect if not destroyed\n if (!this.destroyed) {\n this.reconnect();\n }\n }\n\n private handleEvent(eventType: string, data: string): void {\n try {\n const parsed = JSON.parse(data) as Record<string, unknown>;\n // SSE \"event:\" field is the authoritative event type\n if (eventType) {\n parsed.type = eventType;\n }\n this.onEvent(parsed as SSEEvent);\n } catch {\n // Malformed event data, skip\n }\n }\n\n private reconnect(): void {\n if (this.destroyed) return;\n\n const delay = this.getBackoffDelay();\n this.retryCount++;\n this.retryTimeout = setTimeout(() => {\n this.retryTimeout = null;\n this.connect();\n }, delay);\n }\n\n /** Safari/WebKit throws TypeError instead of AbortError when a fetch is aborted */\n private isAbortError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === 'AbortError') return true;\n if (err.name === 'TypeError' && /load failed|cancelled/i.test(err.message)) return true;\n return false;\n }\n\n private getBackoffDelay(): number {\n const delay = this.retryDelay * Math.pow(2, this.retryCount);\n const jitter = delay * 0.25 * (Math.random() * 2 - 1);\n return Math.min(delay + jitter, this.maxRetryDelay);\n }\n}\n","import { SSEManager } from './sse';\nimport type {\n FlagValue,\n FlaggyContext,\n FlaggyClientOptions,\n BatchEvaluateResponse,\n SSEEvent,\n FlagChangeListener,\n ReadyListener,\n ErrorListener,\n} from './types';\n\ntype EventMap = {\n change: FlagChangeListener;\n ready: ReadyListener;\n error: ErrorListener;\n};\n\nexport class FlaggyClient {\n private readonly serverUrl: string;\n private readonly apiKey: string;\n private readonly flags: string[];\n private readonly enableStreaming: boolean;\n private readonly sseRetryDelay: number;\n private readonly sseMaxRetryDelay: number;\n\n private context: FlaggyContext;\n private cache = new Map<string, FlagValue>();\n private _ready = false;\n private _error: Error | null = null;\n private sseManager: SSEManager | null = null;\n private contextAbortController: AbortController | null = null;\n\n private listeners: {\n change: Set<FlagChangeListener>;\n ready: Set<ReadyListener>;\n error: Set<ErrorListener>;\n } = {\n change: new Set(),\n ready: new Set(),\n error: new Set(),\n };\n\n constructor(options: FlaggyClientOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.flags = options.flags;\n this.context = options.context ?? {};\n this.enableStreaming = options.enableStreaming ?? true;\n this.sseRetryDelay = options.sseRetryDelay ?? 1000;\n this.sseMaxRetryDelay = options.sseMaxRetryDelay ?? 30_000;\n }\n\n get ready(): boolean {\n return this._ready;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n async initialize(): Promise<void> {\n try {\n const response = await this.fetchApi<BatchEvaluateResponse>(\n '/api/v1/evaluate/batch',\n { flags: this.flags, context: this.context },\n );\n this.applyBatchResult(response);\n this._ready = true;\n this.emit('ready');\n } catch (err: unknown) {\n this._error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', this._error);\n }\n\n if (this.enableStreaming) {\n this.startSSE();\n }\n }\n\n getFlag<T extends FlagValue>(key: string, defaultValue: T): T {\n if (!this._ready || !this.cache.has(key)) {\n return defaultValue;\n }\n return this.cache.get(key) as T;\n }\n\n async setContext(context: FlaggyContext): Promise<void> {\n this.context = context;\n this.contextAbortController?.abort();\n const controller = new AbortController();\n this.contextAbortController = controller;\n\n try {\n const response = await this.fetchApi<BatchEvaluateResponse>(\n '/api/v1/evaluate/batch',\n { flags: this.flags, context },\n controller.signal,\n );\n if (controller.signal.aborted) return;\n this.applyBatchResult(response);\n } catch (err: unknown) {\n if (err instanceof Error && err.name === 'AbortError') return;\n this._error = err instanceof Error ? err : new Error(String(err));\n this.emit('error', this._error);\n }\n }\n\n on<E extends keyof EventMap>(event: E, listener: EventMap[E]): () => void {\n (this.listeners[event] as Set<EventMap[E]>).add(listener);\n return () => {\n (this.listeners[event] as Set<EventMap[E]>).delete(listener);\n };\n }\n\n destroy(): void {\n this.sseManager?.destroy();\n this.sseManager = null;\n this.contextAbortController?.abort();\n this.contextAbortController = null;\n this.listeners.change.clear();\n this.listeners.ready.clear();\n this.listeners.error.clear();\n }\n\n private startSSE(): void {\n this.sseManager = new SSEManager({\n url: `${this.serverUrl}/api/v1/stream`,\n apiKey: this.apiKey,\n onEvent: (event) => this.handleSSEEvent(event),\n onError: (err) => this.emit('error', err),\n retryDelay: this.sseRetryDelay,\n maxRetryDelay: this.sseMaxRetryDelay,\n });\n this.sseManager.connect();\n }\n\n private async handleSSEEvent(event: SSEEvent): Promise<void> {\n // Ignore connection confirmation\n if (event.type === 'connected') return;\n\n // Any flag/rule/segment change — re-evaluate all flags via batch\n try {\n const response = await this.fetchApi<BatchEvaluateResponse>(\n '/api/v1/evaluate/batch',\n { flags: this.flags, context: this.context },\n );\n this.applyBatchResult(response);\n } catch {\n // Failed to re-evaluate, keep previous cached values\n }\n }\n\n private applyBatchResult(response: BatchEvaluateResponse): void {\n const newCache = new Map<string, FlagValue>();\n for (const flag of response.results) {\n newCache.set(flag.flag_key, flag.value);\n }\n\n // Emit changes for any values that differ\n for (const [key, newValue] of newCache) {\n const oldValue = this.cache.get(key);\n if (oldValue !== newValue) {\n this.emit('change', key, newValue);\n }\n }\n\n // Emit changes for keys that were removed\n for (const key of this.cache.keys()) {\n if (!newCache.has(key)) {\n this.emit('change', key, undefined as unknown as FlagValue);\n }\n }\n\n this.cache = newCache;\n }\n\n private emit(event: 'change', key: string, value: FlagValue): void;\n private emit(event: 'ready'): void;\n private emit(event: 'error', error: Error): void;\n private emit(event: keyof EventMap, ...args: unknown[]): void {\n if (event === 'change') {\n for (const listener of this.listeners.change) {\n listener(args[0] as string, args[1] as FlagValue);\n }\n } else if (event === 'ready') {\n for (const listener of this.listeners.ready) {\n listener();\n }\n } else if (event === 'error') {\n for (const listener of this.listeners.error) {\n listener(args[0] as Error);\n }\n }\n }\n\n private async fetchApi<T>(\n path: string,\n body: unknown,\n signal?: AbortSignal,\n ): Promise<T> {\n const response = await fetch(`${this.serverUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n signal,\n });\n if (!response.ok) {\n throw new Error(`Flaggy API error: ${response.status} ${response.statusText}`);\n }\n return response.json() as Promise<T>;\n }\n}\n","import { createContext } from 'react';\nimport type { FlaggyClient } from '../client';\n\nexport interface FlaggyContextValue {\n client: FlaggyClient;\n ready: boolean;\n error: Error | null;\n}\n\nexport const FlaggyReactContext = createContext<FlaggyContextValue | null>(null);\n","import { useContext, useCallback, useSyncExternalStore } from 'react';\nimport { FlaggyReactContext } from './context';\nimport type { FlagValue } from '../types';\n\nexport function useFlag<T extends FlagValue>(key: string, defaultValue: T): T {\n const ctx = useContext(FlaggyReactContext);\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n if (!ctx) return () => {};\n const unsubChange = ctx.client.on('change', onStoreChange);\n const unsubReady = ctx.client.on('ready', onStoreChange);\n return () => {\n unsubChange();\n unsubReady();\n };\n },\n [ctx],\n );\n\n const getSnapshot = useCallback(\n () => (ctx ? ctx.client.getFlag(key, defaultValue) : defaultValue),\n [ctx, key, defaultValue],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, () => defaultValue);\n}\n","import { useContext } from 'react';\nimport { FlaggyReactContext } from './context';\n\nexport function useFlaggy() {\n const ctx = useContext(FlaggyReactContext);\n\n if (!ctx) {\n throw new Error('[flaggy] useFlaggy() must be used within a <FlaggyProvider>.');\n }\n\n return {\n client: ctx.client,\n ready: ctx.ready,\n error: ctx.error,\n };\n}\n"],"mappings":"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","names":["import_react","import_react","import_react"]}
|
package/dist/react.mjs
CHANGED
|
@@ -23,7 +23,6 @@ function FlaggyProvider({
|
|
|
23
23
|
const clientRef = useRef(null);
|
|
24
24
|
const [ready, setReady] = useState(false);
|
|
25
25
|
const [error, setError] = useState(null);
|
|
26
|
-
const [version, setVersion] = useState(0);
|
|
27
26
|
useEffect(() => {
|
|
28
27
|
const client = new FlaggyClient({
|
|
29
28
|
serverUrl,
|
|
@@ -40,14 +39,10 @@ function FlaggyProvider({
|
|
|
40
39
|
if (!client.ready) setError(err);
|
|
41
40
|
onError?.(err);
|
|
42
41
|
});
|
|
43
|
-
const unsubChange = client.on("change", () => {
|
|
44
|
-
setVersion((v) => v + 1);
|
|
45
|
-
});
|
|
46
42
|
client.initialize();
|
|
47
43
|
return () => {
|
|
48
44
|
unsubReady();
|
|
49
45
|
unsubError();
|
|
50
|
-
unsubChange();
|
|
51
46
|
client.destroy();
|
|
52
47
|
clientRef.current = null;
|
|
53
48
|
};
|
|
@@ -58,19 +53,32 @@ function FlaggyProvider({
|
|
|
58
53
|
clientRef.current.setContext(context);
|
|
59
54
|
}
|
|
60
55
|
}, [contextKey]);
|
|
61
|
-
void version;
|
|
62
56
|
if (!clientRef.current) return null;
|
|
63
57
|
return /* @__PURE__ */ jsx(FlaggyReactContext.Provider, { value: { client: clientRef.current, ready, error }, children });
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
// src/react/useFlag.ts
|
|
67
|
-
import { useContext } from "react";
|
|
61
|
+
import { useContext, useCallback, useSyncExternalStore } from "react";
|
|
68
62
|
function useFlag(key, defaultValue) {
|
|
69
63
|
const ctx = useContext(FlaggyReactContext);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
const subscribe = useCallback(
|
|
65
|
+
(onStoreChange) => {
|
|
66
|
+
if (!ctx) return () => {
|
|
67
|
+
};
|
|
68
|
+
const unsubChange = ctx.client.on("change", onStoreChange);
|
|
69
|
+
const unsubReady = ctx.client.on("ready", onStoreChange);
|
|
70
|
+
return () => {
|
|
71
|
+
unsubChange();
|
|
72
|
+
unsubReady();
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
[ctx]
|
|
76
|
+
);
|
|
77
|
+
const getSnapshot = useCallback(
|
|
78
|
+
() => ctx ? ctx.client.getFlag(key, defaultValue) : defaultValue,
|
|
79
|
+
[ctx, key, defaultValue]
|
|
80
|
+
);
|
|
81
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => defaultValue);
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
// src/react/useFlaggy.ts
|
package/dist/react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/FlaggyProvider.tsx","../src/react/context.ts","../src/react/useFlag.ts","../src/react/useFlaggy.ts"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { FlaggyClient } from '../client';\nimport { FlaggyReactContext } from './context';\nimport type { FlaggyContext } from '../types';\n\nexport interface FlaggyProviderProps {\n serverUrl: string;\n apiKey: string;\n flags: string[];\n context?: FlaggyContext;\n enableStreaming?: boolean;\n /** Called when an error occurs (init failure, SSE error, etc.) */\n onError?: (error: Error) => void;\n children: ReactNode;\n}\n\nexport function FlaggyProvider({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n onError,\n children,\n}: FlaggyProviderProps) {\n const clientRef = useRef<FlaggyClient | null>(null);\n const [ready, setReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n
|
|
1
|
+
{"version":3,"sources":["../src/react/FlaggyProvider.tsx","../src/react/context.ts","../src/react/useFlag.ts","../src/react/useFlaggy.ts"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { FlaggyClient } from '../client';\nimport { FlaggyReactContext } from './context';\nimport type { FlaggyContext } from '../types';\n\nexport interface FlaggyProviderProps {\n serverUrl: string;\n apiKey: string;\n flags: string[];\n context?: FlaggyContext;\n enableStreaming?: boolean;\n /** Called when an error occurs (init failure, SSE error, etc.) */\n onError?: (error: Error) => void;\n children: ReactNode;\n}\n\nexport function FlaggyProvider({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n onError,\n children,\n}: FlaggyProviderProps) {\n const clientRef = useRef<FlaggyClient | null>(null);\n const [ready, setReady] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Create and initialize client when serverUrl or apiKey change\n useEffect(() => {\n const client = new FlaggyClient({\n serverUrl,\n apiKey,\n flags,\n context,\n enableStreaming,\n });\n clientRef.current = client;\n setReady(false);\n setError(null);\n\n const unsubReady = client.on('ready', () => setReady(true));\n const unsubError = client.on('error', (err) => {\n // Only set error state during init; SSE errors are transient\n if (!client.ready) setError(err);\n onError?.(err);\n });\n\n client.initialize();\n\n return () => {\n unsubReady();\n unsubError();\n client.destroy();\n clientRef.current = null;\n };\n }, [serverUrl, apiKey]);\n\n // Update context when it changes (deep comparison via JSON.stringify)\n const contextKey = context ? JSON.stringify(context) : '';\n useEffect(() => {\n if (clientRef.current && context && clientRef.current.ready) {\n clientRef.current.setContext(context);\n }\n }, [contextKey]);\n\n if (!clientRef.current) return null;\n\n return (\n <FlaggyReactContext.Provider value={{ client: clientRef.current, ready, error }}>\n {children}\n </FlaggyReactContext.Provider>\n );\n}\n","import { createContext } from 'react';\nimport type { FlaggyClient } from '../client';\n\nexport interface FlaggyContextValue {\n client: FlaggyClient;\n ready: boolean;\n error: Error | null;\n}\n\nexport const FlaggyReactContext = createContext<FlaggyContextValue | null>(null);\n","import { useContext, useCallback, useSyncExternalStore } from 'react';\nimport { FlaggyReactContext } from './context';\nimport type { FlagValue } from '../types';\n\nexport function useFlag<T extends FlagValue>(key: string, defaultValue: T): T {\n const ctx = useContext(FlaggyReactContext);\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n if (!ctx) return () => {};\n const unsubChange = ctx.client.on('change', onStoreChange);\n const unsubReady = ctx.client.on('ready', onStoreChange);\n return () => {\n unsubChange();\n unsubReady();\n };\n },\n [ctx],\n );\n\n const getSnapshot = useCallback(\n () => (ctx ? ctx.client.getFlag(key, defaultValue) : defaultValue),\n [ctx, key, defaultValue],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, () => defaultValue);\n}\n","import { useContext } from 'react';\nimport { FlaggyReactContext } from './context';\n\nexport function useFlaggy() {\n const ctx = useContext(FlaggyReactContext);\n\n if (!ctx) {\n throw new Error('[flaggy] useFlaggy() must be used within a <FlaggyProvider>.');\n }\n\n return {\n client: ctx.client,\n ready: ctx.ready,\n error: ctx.error,\n };\n}\n"],"mappings":";;;;;AAAA,SAAS,WAAW,QAAQ,gBAAgC;;;ACA5D,SAAS,qBAAqB;AASvB,IAAM,qBAAqB,cAAyC,IAAI;;;AD6D3E;AAtDG,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,YAAY,OAA4B,IAAI;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK;AACxC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAGrD,YAAU,MAAM;AACd,UAAM,SAAS,IAAI,aAAa;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,cAAU,UAAU;AACpB,aAAS,KAAK;AACd,aAAS,IAAI;AAEb,UAAM,aAAa,OAAO,GAAG,SAAS,MAAM,SAAS,IAAI,CAAC;AAC1D,UAAM,aAAa,OAAO,GAAG,SAAS,CAAC,QAAQ;AAE7C,UAAI,CAAC,OAAO,MAAO,UAAS,GAAG;AAC/B,gBAAU,GAAG;AAAA,IACf,CAAC;AAED,WAAO,WAAW;AAElB,WAAO,MAAM;AACX,iBAAW;AACX,iBAAW;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,WAAW,MAAM,CAAC;AAGtB,QAAM,aAAa,UAAU,KAAK,UAAU,OAAO,IAAI;AACvD,YAAU,MAAM;AACd,QAAI,UAAU,WAAW,WAAW,UAAU,QAAQ,OAAO;AAC3D,gBAAU,QAAQ,WAAW,OAAO;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,MAAI,CAAC,UAAU,QAAS,QAAO;AAE/B,SACE,oBAAC,mBAAmB,UAAnB,EAA4B,OAAO,EAAE,QAAQ,UAAU,SAAS,OAAO,MAAM,GAC3E,UACH;AAEJ;;;AE1EA,SAAS,YAAY,aAAa,4BAA4B;AAIvD,SAAS,QAA6B,KAAa,cAAoB;AAC5E,QAAM,MAAM,WAAW,kBAAkB;AAEzC,QAAM,YAAY;AAAA,IAChB,CAAC,kBAA8B;AAC7B,UAAI,CAAC,IAAK,QAAO,MAAM;AAAA,MAAC;AACxB,YAAM,cAAc,IAAI,OAAO,GAAG,UAAU,aAAa;AACzD,YAAM,aAAa,IAAI,OAAO,GAAG,SAAS,aAAa;AACvD,aAAO,MAAM;AACX,oBAAY;AACZ,mBAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAEA,QAAM,cAAc;AAAA,IAClB,MAAO,MAAM,IAAI,OAAO,QAAQ,KAAK,YAAY,IAAI;AAAA,IACrD,CAAC,KAAK,KAAK,YAAY;AAAA,EACzB;AAEA,SAAO,qBAAqB,WAAW,aAAa,MAAM,YAAY;AACxE;;;AC1BA,SAAS,cAAAA,mBAAkB;AAGpB,SAAS,YAAY;AAC1B,QAAM,MAAMC,YAAW,kBAAkB;AAEzC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,8DAA8D;AAAA,EAChF;AAEA,SAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,EACb;AACF;","names":["useContext","useContext"]}
|