@flags-sdk/statsig
Version:
Statsig provider for the Flags SDK
1 lines • 17.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/edge-runtime-hooks.ts"],"names":["Statsig","experiment","autotune","layer"],"mappings":"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","sourcesContent":["export { getProviderData } from './provider';\nimport type { Adapter } from 'flags';\nimport Statsig, {\n type StatsigUser,\n type StatsigOptions,\n type DynamicConfig,\n type Layer,\n} from 'statsig-node-lite';\nimport {\n createEdgeConfigDataAdapter,\n createSyncingHandler,\n} from './edge-runtime-hooks';\n\n// Export the Statsig instance\nexport {\n Statsig,\n type StatsigUser,\n type StatsigOptions,\n type DynamicConfig,\n type Layer,\n};\n\nexport type FeatureGate = ReturnType<\n typeof Statsig.getFeatureGateWithExposureLoggingDisabledSync\n>;\n\ntype AdapterFunction<O> = <T>(\n getValue: (obj: O) => T,\n opts?: { exposureLogging?: boolean },\n) => Adapter<T, StatsigUser>;\n\ntype AdapterResponse = {\n featureGate: AdapterFunction<FeatureGate>;\n dynamicConfig: AdapterFunction<DynamicConfig>;\n experiment: AdapterFunction<DynamicConfig>;\n autotune: AdapterFunction<DynamicConfig>;\n layer: AdapterFunction<Layer>;\n initialize: () => Promise<typeof Statsig>;\n};\n\n/**\n * Create a Statsig adapter for use with the Flags SDK.\n *\n * Can be used to define flags that are powered by Statsig's Feature Management\n * products including Feature Gates and Dynamic Configs.\n */\nexport function createStatsigAdapter(options: {\n /** The Statsig server API key */\n statsigServerApiKey: string;\n /** Optionally override Statsig initialization options */\n statsigOptions?: StatsigOptions;\n /** Provide the project ID to allow links to the Statsig console in the Vercel Toolbar */\n statsigProjectId?: string;\n /** Provide Edge Config details to use the optional Edge Config adapter */\n edgeConfig?: {\n connectionString: string;\n itemKey: string;\n };\n}): AdapterResponse {\n const initializeStatsig = async (): Promise<void> => {\n let dataAdapter: StatsigOptions['dataAdapter'] | undefined;\n if (options.edgeConfig) {\n dataAdapter = await createEdgeConfigDataAdapter({\n edgeConfigItemKey: options.edgeConfig.itemKey,\n edgeConfigConnectionString: options.edgeConfig.connectionString,\n });\n }\n\n await Statsig.initialize(options.statsigServerApiKey, {\n dataAdapter,\n // ID list syncing is disabled by default\n // Can be opted in using `options.statsigOptions`\n initStrategyForIDLists: 'none',\n disableIdListsSync: true,\n // Set a shorter interval during development so developers see changes earlier\n rulesetsSyncIntervalMs:\n process.env.NODE_ENV === 'development' ? 5_000 : undefined,\n ...options.statsigOptions,\n });\n };\n let _initializePromise: Promise<void> | undefined;\n\n /**\n * Initialize the Statsig SDK.\n *\n * This must be called before checking gates/configs or logging events.\n * It is deduplicated to prevent multiple calls from being made.\n * You can pre-initialize the SDK by calling `adapter.initialize()`,\n * otherwise it will be initialized lazily when needed.\n */\n const initialize = async (): Promise<typeof Statsig> => {\n if (!_initializePromise) {\n _initializePromise = initializeStatsig();\n }\n await _initializePromise;\n return Statsig;\n };\n\n const isStatsigUser = (user: unknown): user is StatsigUser => {\n return user != null && typeof user === 'object';\n };\n\n const minSyncDelayMs = options.edgeConfig ? 1_000 : 5_000;\n const syncHandler = createSyncingHandler(minSyncDelayMs);\n\n async function predecide(user?: StatsigUser): Promise<StatsigUser> {\n await initialize();\n syncHandler?.();\n if (!isStatsigUser(user)) {\n throw new Error(\n '@flags-sdk/statsig: Invalid or missing statsigUser from identify. See https://flags-sdk.dev/concepts/identify',\n );\n }\n return user;\n }\n\n function origin(prefix: string) {\n if (!options.statsigProjectId) {\n return () => undefined;\n }\n return (key: string) => {\n // Allow unused suffix to be provided to flags to tell them apart.\n // Used for different treatments of the same Statsig entities.\n const keyPart = key.split('.')[0] ?? '';\n return `https://console.statsig.com/${options.statsigProjectId}/${prefix}/${keyPart}`;\n };\n }\n\n /**\n * Resolve a flag powered by a Feature Gate.\n *\n * Implements `decide` to resolve the Feature Gate with `Statsig.getFeatureGateSync`\n *\n * If a function is provided, the return value of the function called\n * with the feature gate is returned.\n *\n * Implements `origin` to link to the flag in the Flags Explorer\n * if the adapter defines `statsigProjectId`\n */\n function featureGate<T>(\n getValue: (gate: FeatureGate) => T,\n opts?: {\n exposureLogging?: boolean;\n },\n ): Adapter<T, StatsigUser> {\n return {\n origin: origin('gates'),\n decide: async ({ key, entities }) => {\n const user = await predecide(entities);\n const gate = opts?.exposureLogging\n ? Statsig.getFeatureGateSync(user, key)\n : Statsig.getFeatureGateWithExposureLoggingDisabledSync(user, key);\n return getValue(gate);\n },\n };\n }\n\n /**\n * Resolve a flag powered by a Dynamic Config.\n *\n * Implements `decide` to resolve the Dynamic Config with `Statsig.getConfigSync`\n *\n * If a function is provided, the return value of the function called\n * with the dynamic config is returned.\n *\n * Implements `origin` to link to the flag in the Flags Explorer\n * if the adapter defines `statsigProjectId`\n */\n function dynamicConfig<T>(\n getValue: (config: DynamicConfig) => T,\n opts?: {\n exposureLogging?: boolean;\n },\n ): Adapter<T, StatsigUser> {\n return {\n origin: origin('dynamic_configs'),\n decide: async ({ key, entities }) => {\n const user = await predecide(entities);\n const configKey = key.split('.')[0] ?? '';\n const config = opts?.exposureLogging\n ? Statsig.getConfigSync(user, configKey)\n : Statsig.getConfigWithExposureLoggingDisabledSync(user, configKey);\n return getValue(config);\n },\n };\n }\n\n function experiment<T>(\n getValue: (experiment: DynamicConfig) => T,\n opts?: { exposureLogging?: boolean },\n ): Adapter<T, StatsigUser> {\n return {\n origin: origin('experiments'),\n decide: async ({ key, entities }) => {\n const user = await predecide(entities);\n const experiment = opts?.exposureLogging\n ? Statsig.getExperimentSync(user, key)\n : Statsig.getExperimentWithExposureLoggingDisabledSync(user, key);\n return getValue(experiment);\n },\n };\n }\n\n function autotune<T>(\n getValue: (autotune: DynamicConfig) => T,\n opts?: { exposureLogging?: boolean },\n ): Adapter<T, StatsigUser> {\n return {\n origin: origin('autotune'),\n decide: async ({ key, entities }) => {\n const user = await predecide(entities);\n const autotune = opts?.exposureLogging\n ? Statsig.getConfigSync(user, key)\n : Statsig.getConfigWithExposureLoggingDisabledSync(user, key);\n return getValue(autotune);\n },\n };\n }\n\n function layer<T>(\n getValue: (layer: Layer) => T,\n opts?: { exposureLogging?: boolean },\n ): Adapter<T, StatsigUser> {\n return {\n origin: origin('layers'),\n decide: async ({ key, entities }) => {\n const user = await predecide(entities);\n const layer = opts?.exposureLogging\n ? Statsig.getLayerSync(user, key)\n : Statsig.getLayerWithExposureLoggingDisabledSync(user, key);\n return getValue(layer);\n },\n };\n }\n\n return {\n featureGate,\n dynamicConfig,\n experiment,\n autotune,\n layer,\n initialize,\n };\n}\n\nlet defaultStatsigAdapter: AdapterResponse | undefined;\n\nexport function resetDefaultStatsigAdapter() {\n defaultStatsigAdapter = undefined;\n}\n\n/**\n * Equivalent to `createStatsigAdapter` but with default environment variable names.\n *\n * Required:\n * - `STATSIG_SERVER_API_KEY` - Statsig secret server API key\n *\n * Optional:\n * - `STATSIG_PROJECT_ID` - Statsig project ID to enable link in Vercel's Flags Explorer\n * - `EXPERIMENTATION_CONFIG` - Vercel Edge Config connection string\n * - `EXPERIMENTATION_CONFIG_ITEM_KEY` - Vercel Edge Config item key where data is stored\n */\nexport function createDefaultStatsigAdapter(): AdapterResponse {\n if (defaultStatsigAdapter) {\n return defaultStatsigAdapter;\n }\n const statsigServerApiKey = process.env.STATSIG_SERVER_API_KEY as string;\n const statsigProjectId = process.env.STATSIG_PROJECT_ID;\n const edgeConfig = process.env.EXPERIMENTATION_CONFIG;\n const edgeConfigItemKey = process.env.EXPERIMENTATION_CONFIG_ITEM_KEY;\n if (!(edgeConfig && edgeConfigItemKey)) {\n defaultStatsigAdapter = createStatsigAdapter({\n statsigServerApiKey,\n statsigProjectId,\n });\n } else {\n defaultStatsigAdapter = createStatsigAdapter({\n statsigServerApiKey,\n edgeConfig: {\n connectionString: edgeConfig,\n itemKey: edgeConfigItemKey,\n },\n statsigProjectId,\n });\n }\n\n return defaultStatsigAdapter;\n}\n\n/**\n * The default Statsig adapter.\n *\n * This is a convenience object that pre-initializes the Statsig SDK and provides\n * the adapter functions for the Feature Gates, Dynamic Configs, Experiments,\n * Autotunes, and Layers.\n *\n * This is the recommended way to use the Statsig adapter.\n *\n * ```ts\n * // flags.ts\n * import { flag } from 'flags/next';\n * import { statsigAdapter } from '@flags-sdk/statsig';\n *\n * const flag = flag({\n * key: 'my-flag',\n * defaultValue: false,\n * adapter: statsigAdapter.featureGate((gate) => gate.value),\n * });\n * ```\n */\nexport const statsigAdapter: AdapterResponse = {\n featureGate: (...args) => createDefaultStatsigAdapter().featureGate(...args),\n dynamicConfig: (...args) =>\n createDefaultStatsigAdapter().dynamicConfig(...args),\n experiment: (...args) => createDefaultStatsigAdapter().experiment(...args),\n autotune: (...args) => createDefaultStatsigAdapter().autotune(...args),\n layer: (...args) => createDefaultStatsigAdapter().layer(...args),\n initialize: () => createDefaultStatsigAdapter().initialize(),\n};\n","import Statsig from 'statsig-node-lite';\n\ndeclare global {\n var EdgeRuntime: string | undefined;\n}\n\nexport const isEdgeRuntime = (): boolean => {\n return EdgeRuntime !== undefined;\n};\n\n/**\n * The Edge Config Data Adapter is an optional peer dependency that allows\n * the Statsig SDK to retrieve its data from Edge Config instead of over the network.\n */\nexport async function createEdgeConfigDataAdapter(options: {\n edgeConfigItemKey: string;\n edgeConfigConnectionString: string;\n}) {\n // Edge Config adapter requires `@vercel/edge-config` and `statsig-node-vercel`\n // Since it is a peer dependency, we will import it dynamically\n const { EdgeConfigDataAdapter } = await import('statsig-node-vercel');\n const { createClient } = await import('@vercel/edge-config');\n return new EdgeConfigDataAdapter({\n edgeConfigItemKey: options.edgeConfigItemKey,\n edgeConfigClient: createClient(options.edgeConfigConnectionString, {\n // We disable the development cache as Statsig caches for 10 seconds internally,\n // and we want to avoid situations where Statsig tries to read the latest value,\n // but hits the development cache and then caches the outdated value for another 10 seconds,\n // as this would lead to the developer having to wait 20 seconds to see the latest value.\n disableDevelopmentCache: true,\n }),\n });\n}\n\n/**\n * Edge runtime does not support timers outside of a request context.\n *\n * Statsig syncs config specs outside of the request context,\n * so we will support it in triggering config spec synchronization in this case.\n */\nexport const createSyncingHandler = (\n minSyncDelayMs: number,\n): null | (() => void) => {\n // Syncing both in Edge Runtime and Node.js for now, as the sync is otherwise\n // not working during local development.\n //\n // This needs to be fixed in statsig-node-lite in the future.\n //\n // Ideally the Statsig SDK would not sync at all and instead always read from Edge Config,\n // this would provide two benefits:\n // - changes would propagate immediately instead of being cached for 5s or 10s\n // - the broken syncing due to issues in Date.now in Edge Runtime would be irrelevant\n //\n // if (typeof EdgeRuntime === 'undefined') return null;\n let isSyncingConfigSpecs = false;\n let nextConfigSpecSyncTime = Date.now() + minSyncDelayMs;\n return (): void => {\n if (Date.now() >= nextConfigSpecSyncTime && !isSyncingConfigSpecs) {\n try {\n isSyncingConfigSpecs = true;\n const sync = Statsig.syncConfigSpecs().finally(() => {\n isSyncingConfigSpecs = false;\n nextConfigSpecSyncTime = Date.now() + minSyncDelayMs;\n });\n import('@vercel/functions').then(({ waitUntil }) => {\n waitUntil(sync);\n });\n } catch (e) {\n // continue\n }\n }\n };\n};\n"]}