mirror of
https://github.com/vercel/flags.git
synced 2026-09-19 03:49:23 +08:00
(statsig-adapter): Release Statsig Adapter (#43)
* (statsig-adapter): Move Edge Config to peer deps, more consistent api key naming
* Move dynamic imports into initializeStatsig
* Add default adapter pattern
* Add changeset
* Add layerParameter to get config parameters from experiments in a layer
* (statsig-adapter) Focus on feature management (gates, dynamic configs), add origin, add exposure logging option
* (statsig-adapter) Update key handling and default export
* (refactoring, wip): move some runtime details out of the main implementation of the Statsig adapter
* Use statsig-node-lite
* Switch default environment variable to STATSIG_SERVER_SECRET"
* (statsig-adapter) Add readme
* Update README — Remove details on precompute, add optional peer deps to install command
* Add peerDependenciesMeta
* Update changeset text
* Refactor Statsig Adapter
Add experimentation primitives, disable exposure logging by default, identify StatsigUser instead of `{ statsigUser: StatsigUser }`
* (statsig-adapter) Update default environment variables
* use envvar: EXPERIMENTATION_CONFIG_ITEM_KEY
* Update packages/adapter-statsig/README.md
Co-authored-by: Dominik Ferber <dominik.ferber@gmail.com>
* Update packages/adapter-statsig/README.md
Co-authored-by: Dominik Ferber <dominik.ferber@gmail.com>
* Update README, docstrings
* upgrade statsig-node-lite
* remove edge config devDep
* reduce propagation time of Statsig updates
* update deps
* update lockfile
* prefix error
* simplify README
* extend package.json
* fix type
* switch example to gate
* expose types of Statsig SDK
* work around syncing issue
---------
Co-authored-by: Dominik Ferber <dominik.ferber@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@flags-sdk/statsig': minor
|
||||
---
|
||||
|
||||
Initial support for Feature Gates and Dynamic Configs
|
||||
@@ -0,0 +1,35 @@
|
||||
# Flags SDK — Statsig Provider
|
||||
|
||||
The [Statsig provider](https://flags-sdk.dev/docs/api-reference/adapters/statsig) for the [Flags SDK](https://flags-sdk.dev/) contains support for Statsig's Feature Gates, Dynamic Config, Experiments, Autotune and Layers.
|
||||
|
||||
## Setup
|
||||
|
||||
The Statsig provider is available in the `@flags-sdk/statsig` module. You can install it with
|
||||
|
||||
```bash
|
||||
pnpm i @flags-sdk/statsig
|
||||
```
|
||||
|
||||
## Provider Instance
|
||||
|
||||
You can import the default adapter instance `statigAdapter` from `@flags-sdk/statsig`:
|
||||
|
||||
```ts
|
||||
import { statsigAdapter } from '@flags-sdk/statsig';
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { flag } from 'flags/next';
|
||||
import { statsigAdapter } from '@flags-sdk/statsig';
|
||||
|
||||
export const marketingGate = flag<boolean>({
|
||||
key: 'marketing_gate',
|
||||
adapter: statsigAdapter.featureGate((config) => config.value),
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Please check out the [Statsig provider documentation](https://flags-sdk.dev/docs/api-reference/adapters/statsig) for more information.
|
||||
@@ -1,10 +1,26 @@
|
||||
{
|
||||
"name": "@flags-sdk/statsig",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"description": "Statsig provider for the Flags SDK",
|
||||
"keywords": [
|
||||
"flags-sdk",
|
||||
"statsig",
|
||||
"dynamic config",
|
||||
"vercel",
|
||||
"edge config",
|
||||
"experiments",
|
||||
"experimentation",
|
||||
"ab testing"
|
||||
],
|
||||
"homepage": "https://flags-sdk.dev/docs",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel/flags/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vercel/flags.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -47,26 +63,23 @@
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/edge-config": "1.2.0",
|
||||
"statsig-node": "5.31.0",
|
||||
"statsig-node-vercel": "0.4.0"
|
||||
"@vercel/edge-config": "^1.2.0",
|
||||
"@vercel/functions": "^1.5.2",
|
||||
"statsig-node-lite": "^0.4.2",
|
||||
"statsig-node-vercel": "^0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.11.17",
|
||||
"@vercel/edge-config": "1.2.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"flags": "workspace:*",
|
||||
"msw": "2.6.4",
|
||||
"rimraf": "6.0.1",
|
||||
"statsig-node": "5.31.0",
|
||||
"statsig-node-vercel": "0.4.0",
|
||||
"tsconfig": "workspace:*",
|
||||
"tsup": "8.0.1",
|
||||
"typescript": "5.6.3",
|
||||
"vite": "5.1.1",
|
||||
"vitest": "1.4.0"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import Statsig from 'statsig-node-lite';
|
||||
|
||||
declare global {
|
||||
var EdgeRuntime: string | undefined;
|
||||
}
|
||||
|
||||
export const isEdgeRuntime = (): boolean => {
|
||||
return EdgeRuntime !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* The Edge Config Data Adapter is an optional peer dependency that allows
|
||||
* the Statsig SDK to retrieve its data from Edge Config instead of over the network.
|
||||
*/
|
||||
export async function createEdgeConfigDataAdapter(options: {
|
||||
edgeConfigItemKey: string;
|
||||
edgeConfigConnectionString: string;
|
||||
}) {
|
||||
// Edge Config adapter requires `@vercel/edge-config` and `statsig-node-vercel`
|
||||
// Since it is a peer dependency, we will import it dynamically
|
||||
const { EdgeConfigDataAdapter } = await import('statsig-node-vercel');
|
||||
const { createClient } = await import('@vercel/edge-config');
|
||||
return new EdgeConfigDataAdapter({
|
||||
edgeConfigItemKey: options.edgeConfigItemKey,
|
||||
edgeConfigClient: createClient(options.edgeConfigConnectionString, {
|
||||
// We disable the development cache as Statsig caches for 10 seconds internally,
|
||||
// and we want to avoid situations where Statsig tries to read the latest value,
|
||||
// but hits the development cache and then caches the outdated value for another 10 seconds,
|
||||
// as this would lead to the developer having to wait 20 seconds to see the latest value.
|
||||
disableDevelopmentCache: true,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edge runtime does not support timers outside of a request context.
|
||||
*
|
||||
* Statsig syncs config specs outside of the request context,
|
||||
* so we will support it in triggering config spec synchronization in this case.
|
||||
*/
|
||||
export const createSyncingHandler = (): null | (() => void) => {
|
||||
// Syncing both in Edge Runtime and Node.js for now, as the sync is otherwise
|
||||
// not working during local development.
|
||||
//
|
||||
// This needs to be fixed in statsig-node-lite in the future.
|
||||
//
|
||||
// Ideally the Statsig SDK would not sync at all and instead always read from Edge Config,
|
||||
// this would provide two benefits:
|
||||
// - changes would propagate immediately instead of being cached for 5s or 10s
|
||||
// - the broken syncing due to issues in Date.now in Edge Runtime would be irrelevant
|
||||
//
|
||||
// if (typeof EdgeRuntime === 'undefined') return null;
|
||||
|
||||
const timerInterval = 5_000;
|
||||
let isSyncingConfigSpecs = false;
|
||||
let nextConfigSpecSyncTime = Date.now() + timerInterval;
|
||||
return (): void => {
|
||||
if (Date.now() >= nextConfigSpecSyncTime && !isSyncingConfigSpecs) {
|
||||
try {
|
||||
isSyncingConfigSpecs = true;
|
||||
const sync = Statsig.syncConfigSpecs().finally(() => {
|
||||
isSyncingConfigSpecs = false;
|
||||
nextConfigSpecSyncTime = Date.now() + timerInterval;
|
||||
});
|
||||
import('@vercel/functions').then(({ waitUntil }) => {
|
||||
waitUntil(sync);
|
||||
});
|
||||
} catch (e) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,50 +1,80 @@
|
||||
export { getProviderData } from './provider';
|
||||
import { Adapter } from 'flags';
|
||||
import Statsig, { type StatsigUser, type StatsigOptions } from 'statsig-node';
|
||||
import { EdgeConfigDataAdapter } from 'statsig-node-vercel';
|
||||
import { createClient } from '@vercel/edge-config';
|
||||
import type { Adapter } from 'flags';
|
||||
import Statsig, {
|
||||
type StatsigUser,
|
||||
type StatsigOptions,
|
||||
type DynamicConfig,
|
||||
type Layer,
|
||||
} from 'statsig-node-lite';
|
||||
import {
|
||||
createEdgeConfigDataAdapter,
|
||||
createSyncingHandler,
|
||||
} from './edge-runtime-hooks';
|
||||
|
||||
interface StatsigUserEntities {
|
||||
statsigUser: StatsigUser;
|
||||
}
|
||||
// Export the Statsig instance
|
||||
export {
|
||||
Statsig,
|
||||
type StatsigUser,
|
||||
type StatsigOptions,
|
||||
type DynamicConfig,
|
||||
type Layer,
|
||||
};
|
||||
|
||||
export type FeatureGate = ReturnType<
|
||||
typeof Statsig.getFeatureGateWithExposureLoggingDisabledSync
|
||||
>;
|
||||
|
||||
type AdapterFunction<O> = <T>(
|
||||
getValue: (obj: O) => T,
|
||||
opts?: { exposureLogging?: boolean },
|
||||
) => Adapter<T, StatsigUser>;
|
||||
|
||||
type AdapterResponse = {
|
||||
featureGate: AdapterFunction<FeatureGate>;
|
||||
dynamicConfig: AdapterFunction<DynamicConfig>;
|
||||
experiment: AdapterFunction<DynamicConfig>;
|
||||
autotune: AdapterFunction<DynamicConfig>;
|
||||
layer: AdapterFunction<Layer>;
|
||||
initialize: () => Promise<typeof Statsig>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a Statsig adapter for use with the Flags SDK.
|
||||
*
|
||||
* The adapter expects to use `statsig-node` and `@vercel/edge-config` to resolve flags via
|
||||
* Statsig Feature Gates, Experiments, DynamicConfigs, Autotunes, and so on.
|
||||
*
|
||||
* It will initialize Statsig and resolve values, and will not log exposures. Exposures
|
||||
* should be logged on the client side to prevent prefetching or middleware from accidentally
|
||||
* triggering exposures when the user has not engaged with a page yet.
|
||||
*
|
||||
* Methods:
|
||||
* - `.()` - Checks a feature gate and returns a boolean value
|
||||
* - `.featureGate()` - Checks a feature gate and returns a value based on the result and rule ID
|
||||
* - `.experiment()` - Checks an experiment and returns a value based on the result and rule ID
|
||||
* Can be used to define flags that are powered by Statsig's Feature Management
|
||||
* products including Feature Gates and Dynamic Configs.
|
||||
*/
|
||||
function createStatsigAdapter(options: {
|
||||
statsigSecretKey: string;
|
||||
export function createStatsigAdapter(options: {
|
||||
/** The Statsig server API key */
|
||||
statsigServerApiKey: string;
|
||||
/** Optionally override Statsig initialization options */
|
||||
statsigOptions?: StatsigOptions;
|
||||
/** Provide the project ID to allow links to the Statsig console in the Vercel Toolbar */
|
||||
statsigProjectId?: string;
|
||||
/** Provide Edge Config details to use the optional Edge Config adapter */
|
||||
edgeConfig?: {
|
||||
connectionString: string;
|
||||
itemKey: string;
|
||||
};
|
||||
}) {
|
||||
const dataAdapter = options.edgeConfig
|
||||
? new EdgeConfigDataAdapter({
|
||||
edgeConfigItemKey: options.edgeConfig.itemKey,
|
||||
edgeConfigClient: createClient(options.edgeConfig.connectionString),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
}): AdapterResponse {
|
||||
const initializeStatsig = async (): Promise<void> => {
|
||||
await Statsig.initialize(options.statsigSecretKey, {
|
||||
let dataAdapter: StatsigOptions['dataAdapter'] | undefined;
|
||||
if (options.edgeConfig) {
|
||||
dataAdapter = await createEdgeConfigDataAdapter({
|
||||
edgeConfigItemKey: options.edgeConfig.itemKey,
|
||||
edgeConfigConnectionString: options.edgeConfig.connectionString,
|
||||
});
|
||||
}
|
||||
|
||||
await Statsig.initialize(options.statsigServerApiKey, {
|
||||
dataAdapter,
|
||||
// ID list syncing is disabled by default
|
||||
// Can be opted in using `options.statsigOptions`
|
||||
initStrategyForIDLists: 'none',
|
||||
disableIdListsSync: true,
|
||||
// Set a shorter interval during development so developers see changes earlier
|
||||
rulesetsSyncIntervalMs:
|
||||
process.env.NODE_ENV === 'development' ? 5_000 : undefined,
|
||||
...options.statsigOptions,
|
||||
});
|
||||
};
|
||||
@@ -58,74 +88,231 @@ function createStatsigAdapter(options: {
|
||||
* You can pre-initialize the SDK by calling `adapter.initialize()`,
|
||||
* otherwise it will be initialized lazily when needed.
|
||||
*/
|
||||
const initialize = async (): Promise<void> => {
|
||||
const initialize = async (): Promise<typeof Statsig> => {
|
||||
if (!_initializePromise) {
|
||||
_initializePromise = initializeStatsig();
|
||||
}
|
||||
await _initializePromise;
|
||||
return Statsig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a Feature Gate and return an object with the boolean value and rule ID
|
||||
*
|
||||
* The key is based on the `key` property of the flag
|
||||
* The entities should extend `{ statsigUser: StatsigUser }`
|
||||
*/
|
||||
const featureGate = <T>(
|
||||
mapValue: (value: boolean, ruleId: string) => T,
|
||||
): Adapter<T, StatsigUserEntities> => {
|
||||
return {
|
||||
decide: async ({ key, entities }) => {
|
||||
await initialize();
|
||||
const result = Statsig.getFeatureGateWithExposureLoggingDisabledSync(
|
||||
entities?.statsigUser as StatsigUser,
|
||||
key,
|
||||
);
|
||||
return mapValue(result.value, result.ruleID);
|
||||
},
|
||||
};
|
||||
const isStatsigUser = (user: unknown): user is StatsigUser => {
|
||||
return user != null && typeof user === 'object';
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a Dynamic Config and return a value based on the result and rule ID.
|
||||
*
|
||||
* The key is based on the `key` property of the flag
|
||||
* The entities should extend `{ statsigUser: StatsigUser }`
|
||||
*
|
||||
* Used as the basis for experiments, dynamic configs, and autotunes
|
||||
*/
|
||||
const dynamicConfig = <T>(
|
||||
mapValue: (value: Record<string, unknown>, ruleId: string) => T,
|
||||
): Adapter<T, StatsigUserEntities> => {
|
||||
return {
|
||||
decide: async ({ key, entities }) => {
|
||||
await initialize();
|
||||
const result = Statsig.getConfigWithExposureLoggingDisabledSync(
|
||||
entities?.statsigUser as StatsigUser,
|
||||
key,
|
||||
);
|
||||
return mapValue(result.value, result.getRuleID());
|
||||
},
|
||||
};
|
||||
};
|
||||
const syncHandler = createSyncingHandler();
|
||||
|
||||
/**
|
||||
* Check a feature gate and return a boolean value
|
||||
*
|
||||
* Because the ruleID is not returned, this default usage is not suited for
|
||||
* feature gates with metric lifts. For such usage, see `adapter.featureGate`
|
||||
* and include the ruleID in the return value.
|
||||
*/
|
||||
function statsigAdapter(): Adapter<boolean, StatsigUserEntities> {
|
||||
return featureGate((value) => value);
|
||||
async function predecide(user?: StatsigUser): Promise<StatsigUser> {
|
||||
await initialize();
|
||||
syncHandler?.();
|
||||
if (!isStatsigUser(user)) {
|
||||
throw new Error(
|
||||
'@flags-sdk/statsig: Invalid or missing statsigUser from identify. See https://flags-sdk.dev/concepts/identify',
|
||||
);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
statsigAdapter.featureGate = featureGate;
|
||||
statsigAdapter.experiment = dynamicConfig;
|
||||
statsigAdapter.dynamicConfig = dynamicConfig;
|
||||
statsigAdapter.autotune = dynamicConfig;
|
||||
statsigAdapter.initialize = initialize;
|
||||
return statsigAdapter;
|
||||
function origin(prefix: string) {
|
||||
if (!options.statsigProjectId) {
|
||||
return () => undefined;
|
||||
}
|
||||
return (key: string) => {
|
||||
// Allow unused suffix to be provided to flags to tell them apart.
|
||||
// Used for different treatments of the same Statsig entities.
|
||||
const keyPart = key.split('.')[0] ?? '';
|
||||
return `https://console.statsig.com/${options.statsigProjectId}/${prefix}/${keyPart}`;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a flag powered by a Feature Gate.
|
||||
*
|
||||
* Implements `decide` to resolve the Feature Gate with `Statsig.getFeatureGateSync`
|
||||
*
|
||||
* If a function is provided, the return value of the function called
|
||||
* with the feature gate is returned.
|
||||
*
|
||||
* Implements `origin` to link to the flag in the Flags Explorer
|
||||
* if the adapter defines `statsigProjectId`
|
||||
*/
|
||||
function featureGate<T>(
|
||||
getValue: (gate: FeatureGate) => T,
|
||||
opts?: {
|
||||
exposureLogging?: boolean;
|
||||
},
|
||||
): Adapter<T, StatsigUser> {
|
||||
return {
|
||||
origin: origin('gates'),
|
||||
decide: async ({ key, entities }) => {
|
||||
const user = await predecide(entities);
|
||||
const gate = opts?.exposureLogging
|
||||
? Statsig.getFeatureGateSync(user, key)
|
||||
: Statsig.getFeatureGateWithExposureLoggingDisabledSync(user, key);
|
||||
return getValue(gate);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a flag powered by a Dynamic Config.
|
||||
*
|
||||
* Implements `decide` to resolve the Dynamic Config with `Statsig.getConfigSync`
|
||||
*
|
||||
* If a function is provided, the return value of the function called
|
||||
* with the dynamic config is returned.
|
||||
*
|
||||
* Implements `origin` to link to the flag in the Flags Explorer
|
||||
* if the adapter defines `statsigProjectId`
|
||||
*/
|
||||
function dynamicConfig<T>(
|
||||
getValue: (config: DynamicConfig) => T,
|
||||
opts?: {
|
||||
exposureLogging?: boolean;
|
||||
},
|
||||
): Adapter<T, StatsigUser> {
|
||||
return {
|
||||
origin: origin('dynamic_configs'),
|
||||
decide: async ({ key, entities }) => {
|
||||
const user = await predecide(entities);
|
||||
const configKey = key.split('.')[0] ?? '';
|
||||
const config = opts?.exposureLogging
|
||||
? Statsig.getConfigSync(user, configKey)
|
||||
: Statsig.getConfigWithExposureLoggingDisabledSync(user, configKey);
|
||||
return getValue(config);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function experiment<T>(
|
||||
getValue: (experiment: DynamicConfig) => T,
|
||||
opts?: { exposureLogging?: boolean },
|
||||
): Adapter<T, StatsigUser> {
|
||||
return {
|
||||
origin: origin('experiments'),
|
||||
decide: async ({ key, entities }) => {
|
||||
const user = await predecide(entities);
|
||||
const experiment = opts?.exposureLogging
|
||||
? Statsig.getExperimentSync(user, key)
|
||||
: Statsig.getExperimentWithExposureLoggingDisabledSync(user, key);
|
||||
return getValue(experiment);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function autotune<T>(
|
||||
getValue: (autotune: DynamicConfig) => T,
|
||||
opts?: { exposureLogging?: boolean },
|
||||
): Adapter<T, StatsigUser> {
|
||||
return {
|
||||
origin: origin('autotune'),
|
||||
decide: async ({ key, entities }) => {
|
||||
const user = await predecide(entities);
|
||||
const autotune = opts?.exposureLogging
|
||||
? Statsig.getConfigSync(user, key)
|
||||
: Statsig.getConfigWithExposureLoggingDisabledSync(user, key);
|
||||
return getValue(autotune);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function layer<T>(
|
||||
getValue: (layer: Layer) => T,
|
||||
opts?: { exposureLogging?: boolean },
|
||||
): Adapter<T, StatsigUser> {
|
||||
return {
|
||||
origin: origin('layers'),
|
||||
decide: async ({ key, entities }) => {
|
||||
const user = await predecide(entities);
|
||||
const layer = opts?.exposureLogging
|
||||
? Statsig.getLayerSync(user, key)
|
||||
: Statsig.getLayerWithExposureLoggingDisabledSync(user, key);
|
||||
return getValue(layer);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
featureGate,
|
||||
dynamicConfig,
|
||||
experiment,
|
||||
autotune,
|
||||
layer,
|
||||
initialize,
|
||||
};
|
||||
}
|
||||
|
||||
export { createStatsigAdapter };
|
||||
let defaultStatsigAdapter: AdapterResponse | undefined;
|
||||
|
||||
export function resetDefaultStatsigAdapter() {
|
||||
defaultStatsigAdapter = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to `createStatsigAdapter` but with default environment variable names.
|
||||
*
|
||||
* Required:
|
||||
* - `STATSIG_SERVER_API_KEY` - Statsig secret server API key
|
||||
*
|
||||
* Optional:
|
||||
* - `STATSIG_PROJECT_ID` - Statsig project ID to enable link in Vercel's Flags Explorer
|
||||
* - `EXPERIMENTATION_CONFIG` - Vercel Edge Config connection string
|
||||
* - `EXPERIMENTATION_CONFIG_ITEM_KEY` - Vercel Edge Config item key where data is stored
|
||||
*/
|
||||
export function createDefaultStatsigAdapter(): AdapterResponse {
|
||||
if (defaultStatsigAdapter) {
|
||||
return defaultStatsigAdapter;
|
||||
}
|
||||
const statsigServerApiKey = process.env.STATSIG_SERVER_API_KEY as string;
|
||||
const statsigProjectId = process.env.STATSIG_PROJECT_ID;
|
||||
const edgeConfig = process.env.EXPERIMENTATION_CONFIG;
|
||||
const edgeConfigItemKey = process.env.EXPERIMENTATION_CONFIG_ITEM_KEY;
|
||||
if (!(edgeConfig && edgeConfigItemKey)) {
|
||||
defaultStatsigAdapter = createStatsigAdapter({
|
||||
statsigServerApiKey,
|
||||
statsigProjectId,
|
||||
});
|
||||
} else {
|
||||
defaultStatsigAdapter = createStatsigAdapter({
|
||||
statsigServerApiKey,
|
||||
edgeConfig: {
|
||||
connectionString: edgeConfig,
|
||||
itemKey: edgeConfigItemKey,
|
||||
},
|
||||
statsigProjectId,
|
||||
});
|
||||
}
|
||||
|
||||
return defaultStatsigAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default Statsig adapter.
|
||||
*
|
||||
* This is a convenience object that pre-initializes the Statsig SDK and provides
|
||||
* the adapter functions for the Feature Gates, Dynamic Configs, Experiments,
|
||||
* Autotunes, and Layers.
|
||||
*
|
||||
* This is the recommended way to use the Statsig adapter.
|
||||
*
|
||||
* ```ts
|
||||
* // flags.ts
|
||||
* import { flag } from 'flags/next';
|
||||
* import { statsigAdapter } from '@flags-sdk/statsig';
|
||||
*
|
||||
* const flag = flag({
|
||||
* key: 'my-flag',
|
||||
* defaultValue: false,
|
||||
* adapter: statsigAdapter.featureGate((gate) => gate.value),
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const statsigAdapter: AdapterResponse = {
|
||||
featureGate: (...args) => createDefaultStatsigAdapter().featureGate(...args),
|
||||
dynamicConfig: (...args) =>
|
||||
createDefaultStatsigAdapter().dynamicConfig(...args),
|
||||
experiment: (...args) => createDefaultStatsigAdapter().experiment(...args),
|
||||
autotune: (...args) => createDefaultStatsigAdapter().autotune(...args),
|
||||
layer: (...args) => createDefaultStatsigAdapter().layer(...args),
|
||||
initialize: () => createDefaultStatsigAdapter().initialize(),
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ describe('getProviderData', () => {
|
||||
it('should fetch and return', async () => {
|
||||
await expect(
|
||||
getProviderData({
|
||||
consoleApiKey: 'console-this-is-a-test-token',
|
||||
statsigConsoleApiKey: 'console-this-is-a-test-token',
|
||||
projectId: 'project-id-placeholder',
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
@@ -302,7 +302,7 @@ describe('getProviderData', () => {
|
||||
it('should return appropriate hints', async () => {
|
||||
await expect(
|
||||
getProviderData({
|
||||
consoleApiKey: '',
|
||||
statsigConsoleApiKey: '',
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
definitions: {},
|
||||
|
||||
@@ -44,7 +44,7 @@ interface StatsigExperimentsResponse {
|
||||
}
|
||||
|
||||
export async function getProviderData(options: {
|
||||
consoleApiKey: string;
|
||||
statsigConsoleApiKey: string;
|
||||
/**
|
||||
* Required to set the `origin` property on the flag definitions.
|
||||
*/
|
||||
@@ -52,7 +52,7 @@ export async function getProviderData(options: {
|
||||
}): Promise<ProviderData> {
|
||||
const hints = [];
|
||||
|
||||
if (!options.consoleApiKey) {
|
||||
if (!options.statsigConsoleApiKey) {
|
||||
hints.push({
|
||||
key: 'statsig/missing-api-key',
|
||||
text: 'Missing Statsig Console API Key',
|
||||
@@ -121,7 +121,7 @@ export async function getProviderData(options: {
|
||||
/**
|
||||
* Fetch all Feature Gates.
|
||||
*/
|
||||
async function getFeatureGates(options: { consoleApiKey: string }) {
|
||||
async function getFeatureGates(options: { statsigConsoleApiKey: string }) {
|
||||
const data: StatsigFeatureGateResponse['data'] = [];
|
||||
|
||||
let suffix: string | null = '/console/v1/gates';
|
||||
@@ -131,7 +131,7 @@ async function getFeatureGates(options: { consoleApiKey: string }) {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'STATSIG-API-KEY': options.consoleApiKey,
|
||||
'STATSIG-API-KEY': options.statsigConsoleApiKey,
|
||||
},
|
||||
// @ts-expect-error some Next.js versions need this
|
||||
cache: 'no-store',
|
||||
@@ -157,7 +157,7 @@ async function getFeatureGates(options: { consoleApiKey: string }) {
|
||||
/**
|
||||
* Fetch all experiments.
|
||||
*/
|
||||
async function getExperiments(options: { consoleApiKey: string }) {
|
||||
async function getExperiments(options: { statsigConsoleApiKey: string }) {
|
||||
const data: StatsigExperimentsResponse['data'] = [];
|
||||
|
||||
let suffix: string | null = '/console/v1/experiments';
|
||||
@@ -167,7 +167,7 @@ async function getExperiments(options: { consoleApiKey: string }) {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'STATSIG-API-KEY': options.consoleApiKey,
|
||||
'STATSIG-API-KEY': options.statsigConsoleApiKey,
|
||||
},
|
||||
// @ts-expect-error some Next.js versions need this
|
||||
cache: 'no-store',
|
||||
|
||||
Generated
+1085
-1039
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user