UNPKG

@flagvault/sdk

Version:

Lightweight JavaScript SDK for FlagVault with graceful error handling and built-in React hooks for seamless feature flag integration.

401 lines 15.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FlagVaultAPIError = exports.FlagVaultNetworkError = exports.FlagVaultAuthenticationError = exports.FlagVaultError = void 0; exports.useFeatureFlag = useFeatureFlag; exports.useFeatureFlagCached = useFeatureFlagCached; /** * Base exception for FlagVault SDK errors. */ class FlagVaultError extends Error { constructor(message) { super(message); this.name = "FlagVaultError"; } } exports.FlagVaultError = FlagVaultError; /** * Raised when authentication fails. */ class FlagVaultAuthenticationError extends FlagVaultError { constructor(message) { super(message); this.name = "FlagVaultAuthenticationError"; } } exports.FlagVaultAuthenticationError = FlagVaultAuthenticationError; /** * Raised when network requests fail. */ class FlagVaultNetworkError extends FlagVaultError { constructor(message) { super(message); this.name = "FlagVaultNetworkError"; } } exports.FlagVaultNetworkError = FlagVaultNetworkError; /** * Raised when the API returns an error response. */ class FlagVaultAPIError extends FlagVaultError { constructor(message) { super(message); this.name = "FlagVaultAPIError"; } } exports.FlagVaultAPIError = FlagVaultAPIError; /** * FlagVault SDK for feature flag management. * * This SDK allows you to easily integrate feature flags into your JavaScript/TypeScript applications. * Feature flags (also known as feature toggles) allow you to enable or disable features in your * application without deploying new code. * * ## Installation * * ```bash * npm install @flagvault/sdk * # or * yarn add @flagvault/sdk * ``` * * ## Basic Usage * * ```typescript * import FlagVaultSDK from '@flagvault/sdk'; * * const sdk = new FlagVaultSDK({ * apiKey: 'live_your-api-key-here' // Use 'test_' prefix for test environment * }); * * // Check if a feature flag is enabled * const isEnabled = await sdk.isEnabled('my-feature-flag'); * if (isEnabled) { * // Feature is enabled, run feature code * } else { * // Feature is disabled, run fallback code * } * ``` * * ## Graceful Error Handling * * The SDK automatically handles errors gracefully by returning default values: * * ```typescript * // No try/catch needed - errors are handled gracefully * const isEnabled = await sdk.isEnabled('my-feature-flag', false); * * // On network error, you'll see: * // FlagVault: Failed to connect to API for flag 'my-feature-flag', using default: false * * // On authentication error: * // FlagVault: Invalid API credentials for flag 'my-feature-flag', using default: false * * // On missing flag: * // FlagVault: Flag 'my-feature-flag' not found, using default: false * ``` * * ## Advanced Error Handling * * For custom error handling, you can still catch exceptions for parameter validation: * * ```typescript * try { * const isEnabled = await sdk.isEnabled('my-feature-flag', false); * // ... * } catch (error) { * // Only throws for invalid parameters (empty flagKey) * console.error('Parameter validation error:', error.message); * } * ``` * * @group Core */ class FlagVaultSDK { /** * Creates a new instance of the FlagVault SDK. * * @param config - Configuration options for the SDK * @throws Error if apiKey is not provided */ constructor(config) { const { apiKey, baseUrl = "https://api.flagvault.com", timeout = 10000, } = config; if (!apiKey) { throw new Error("API Key is required to initialize the SDK."); } this.apiKey = apiKey; this.baseUrl = baseUrl; this.timeout = timeout; } /** * Checks if a feature flag is enabled. * * @param flagKey - The key for the feature flag * @param defaultValue - Default value to return on error (defaults to false) * @returns A promise that resolves to a boolean indicating if the feature is enabled * @throws Error if flagKey is not provided */ isEnabled(flagKey_1) { return __awaiter(this, arguments, void 0, function* (flagKey, defaultValue = false) { var _a; if (!flagKey) { throw new Error("flagKey is required to check if a feature is enabled."); } const url = `${this.baseUrl}/api/feature-flag/${flagKey}/enabled`; try { // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); const response = yield fetch(url, { method: "GET", headers: { "X-API-Key": this.apiKey, }, signal: controller.signal, }); clearTimeout(timeoutId); // Handle authentication errors if (response.status === 401) { console.warn(`FlagVault: Invalid API credentials for flag '${flagKey}', using default: ${defaultValue}`); return defaultValue; } else if (response.status === 403) { console.warn(`FlagVault: Access forbidden for flag '${flagKey}', using default: ${defaultValue}`); return defaultValue; } // Handle flag not found if (response.status === 404) { console.warn(`FlagVault: Flag '${flagKey}' not found, using default: ${defaultValue}`); return defaultValue; } // Handle other HTTP errors if (!response.ok) { let errorMessage; try { const errorData = yield response.json(); errorMessage = errorData.message || `HTTP ${response.status}`; } catch (_b) { errorMessage = `HTTP ${response.status}: ${response.statusText}`; } console.warn(`FlagVault: API error for flag '${flagKey}' (${errorMessage}), using default: ${defaultValue}`); return defaultValue; } // Parse successful response let data; try { data = yield response.json(); } catch (_c) { console.warn(`FlagVault: Invalid JSON response for flag '${flagKey}', using default: ${defaultValue}`); return defaultValue; } return (_a = data.enabled) !== null && _a !== void 0 ? _a : false; } catch (error) { // Handle network and timeout errors gracefully if (error instanceof DOMException && error.name === "AbortError") { console.warn(`FlagVault: Request timed out for flag '${flagKey}' after ${this.timeout}ms, using default: ${defaultValue}`); return defaultValue; } if (error instanceof TypeError) { console.warn(`FlagVault: Failed to connect to API for flag '${flagKey}', using default: ${defaultValue}`); return defaultValue; } console.warn(`FlagVault: Network error for flag '${flagKey}' (${error}), using default: ${defaultValue}`); return defaultValue; } }); } } exports.default = FlagVaultSDK; /** * React hook for checking feature flag status. * * @param sdk - FlagVault SDK instance * @param flagKey - The feature flag key to check * @param defaultValue - Default value to use if flag cannot be loaded * @returns Object containing isEnabled, isLoading, and error states * * @example * ```tsx * import FlagVaultSDK, { useFeatureFlag } from '@flagvault/sdk'; * * const sdk = new FlagVaultSDK({ apiKey: 'live_your-api-key' }); * * function MyComponent() { * const { isEnabled, isLoading, error } = useFeatureFlag(sdk, 'new-feature', false); * * if (isLoading) return <div>Loading...</div>; * if (error) return <div>Error: {error.message}</div>; * * return isEnabled ? <NewFeature /> : <OldFeature />; * } * ``` * * @group React Hooks */ function useFeatureFlag(sdk, flagKey, defaultValue = false) { // Note: This requires React to be installed as a peer dependency // We use a try/catch to gracefully handle cases where React is not available try { // Use any to avoid TypeScript errors with dynamic imports // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-require-imports const React = require("react"); const { useState, useEffect } = React; const [isEnabled, setIsEnabled] = useState(defaultValue); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; function checkFlag() { return __awaiter(this, void 0, void 0, function* () { try { setIsLoading(true); const enabled = yield sdk.isEnabled(flagKey, defaultValue); if (isMounted) { setIsEnabled(enabled); setError(null); } } catch (err) { if (isMounted) { const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); setIsEnabled(defaultValue); } } finally { if (isMounted) { setIsLoading(false); } } }); } checkFlag(); return () => { isMounted = false; }; }, [sdk, flagKey, defaultValue]); return { isEnabled, isLoading, error }; } catch (_a) { throw new Error("useFeatureFlag requires React to be installed as a peer dependency. " + "Please install React: npm install react"); } } /** * React hook for checking feature flag status with caching. * Reduces API calls by caching flag values for a specified TTL. * * @param sdk - FlagVault SDK instance * @param flagKey - The feature flag key to check * @param defaultValue - Default value to use if flag cannot be loaded * @param cacheTTL - Cache time-to-live in milliseconds (default: 5 minutes) * @returns Object containing isEnabled, isLoading, and error states * * @example * ```tsx * import FlagVaultSDK, { useFeatureFlagCached } from '@flagvault/sdk'; * * const sdk = new FlagVaultSDK({ apiKey: 'live_your-api-key' }); * * function MyComponent() { * const { isEnabled, isLoading } = useFeatureFlagCached( * sdk, * 'new-feature', * false, * 300000 // 5 minutes cache * ); * * return isEnabled ? <NewFeature /> : <OldFeature />; * } * ``` * * @group React Hooks */ function useFeatureFlagCached(sdk, flagKey, defaultValue = false, cacheTTL = 5 * 60 * 1000) { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-require-imports const React = require("react"); const { useState, useEffect } = React; const [isEnabled, setIsEnabled] = useState(defaultValue); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; function checkFlag() { return __awaiter(this, void 0, void 0, function* () { try { setIsLoading(true); // Check cache first const cacheKey = `flagvault_${flagKey}`; const cachedData = getCachedFlag(cacheKey, cacheTTL); if (cachedData !== null) { if (isMounted) { setIsEnabled(cachedData); setError(null); setIsLoading(false); } return; } // Fetch from API const enabled = yield sdk.isEnabled(flagKey, defaultValue); // Update cache setCachedFlag(cacheKey, enabled); if (isMounted) { setIsEnabled(enabled); setError(null); } } catch (err) { if (isMounted) { const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); setIsEnabled(defaultValue); } } finally { if (isMounted) { setIsLoading(false); } } }); } checkFlag(); return () => { isMounted = false; }; }, [sdk, flagKey, defaultValue, cacheTTL]); return { isEnabled, isLoading, error }; } catch (_a) { throw new Error("useFeatureFlagCached requires React to be installed as a peer dependency. " + "Please install React: npm install react"); } } // Simple in-memory cache for React hooks const flagCache = new Map(); function getCachedFlag(key, ttl) { const cached = flagCache.get(key); if (!cached) return null; const now = Date.now(); if (now - cached.timestamp > ttl) { flagCache.delete(key); return null; } return cached.value; } function setCachedFlag(key, value) { flagCache.set(key, { value, timestamp: Date.now() }); } //# sourceMappingURL=index.js.map