From 6efb2c6519f440152e067c2fe8cdb0e3703941de Mon Sep 17 00:00:00 2001 From: Dominik Ferber Date: Fri, 1 May 2026 08:15:50 +0200 Subject: [PATCH] [@flags-sdk/statsig] upgrade to @statsig/statsig-node-core --- .changeset/statsig-node-core-migration.md | 20 ++ apps/docs/content/docs/providers/statsig.mdx | 54 ++-- packages/adapter-statsig/package.json | 6 +- .../adapter-statsig/src/edge-runtime-hooks.ts | 73 ------ packages/adapter-statsig/src/index.ts | 158 +++++++----- pnpm-lock.yaml | 242 ++++++++++++++---- 6 files changed, 333 insertions(+), 220 deletions(-) create mode 100644 .changeset/statsig-node-core-migration.md delete mode 100644 packages/adapter-statsig/src/edge-runtime-hooks.ts diff --git a/.changeset/statsig-node-core-migration.md b/.changeset/statsig-node-core-migration.md new file mode 100644 index 00000000..57971290 --- /dev/null +++ b/.changeset/statsig-node-core-migration.md @@ -0,0 +1,20 @@ +--- +"@flags-sdk/statsig": major +--- + +Migrate to `@statsig/statsig-node-core` (Statsig's Rust-based server SDK). + +This is a breaking change. The underlying Statsig SDK is now instance-based instead of a singleton, and several method names and option keys have changed. + +**Breaking changes** + +- The exported `Statsig` is now a class (`new Statsig(key, options)`), not a singleton. Methods such as `Statsig.getFeatureGateSync` no longer exist — use the instance returned by `statsigAdapter.initialize()` and call `getFeatureGate(user, key)` etc. +- Sync method variants (`*Sync`) and `*WithExposureLoggingDisabledSync` are removed. Pass `{ disableExposureLogging: true }` as the third argument instead. +- The `DynamicConfig` and `Experiment` types are now distinct (the adapter's `experiment` getter receives an `Experiment`). +- `statsigOptions` keys changed: use `specsSyncIntervalMs` (was `rulesetsSyncIntervalMs`), `enableIdLists` (was `disableIdListsSync`/`initStrategyForIDLists`), and `dataStore` (was `dataAdapter`). +- `getClientInitializeResponse` now returns a JSON `string` and accepts `{ hashAlgorithm: 'djb2' }` instead of `{ hash: 'djb2' }`. +- The Edge Runtime workaround hooks were removed. The new SDK uses native Node bindings (NAPI) and runs on Node.js only — including Vercel's Fluid Compute. It is not compatible with the Edge Runtime. + +**Internal changes** + +- Drops the `statsig-node-vercel` dependency. The Edge Config integration is now implemented inline using a custom `DataStore` that reads from `@vercel/edge-config`. diff --git a/apps/docs/content/docs/providers/statsig.mdx b/apps/docs/content/docs/providers/statsig.mdx index 65aea372..520df3eb 100644 --- a/apps/docs/content/docs/providers/statsig.mdx +++ b/apps/docs/content/docs/providers/statsig.mdx @@ -222,7 +222,7 @@ When using server-side rendering the server can inline the information needed to Your application roughly needs to follow these steps: 1. Call the same `identify` function your feature flags use to get the Statsig user. -2. Call `statsigAdapter.initialize()` to initialize the `statsig-node-lite` SDK. +2. Call `statsigAdapter.initialize()` to initialize the `@statsig/statsig-node-core` SDK. 3. Prepare the bootstrap data on the server, and pass it to the client. 4. Use the bootstrap data on the browser to initialize a client and set up the Statsig React provider. @@ -232,7 +232,7 @@ Below are the most critical pieces you need to implement this, which is meant as ```tsx title="app/(example)/layout.tsx#next" import { cookies, headers } from "next/headers"; -import { statsigAdapter } from "@flags-sdk/statsig"; +import { statsigAdapter, StatsigUser } from "@flags-sdk/statsig"; import { DynamicStatsigProvider } from "./dynamic-statsig-provider"; // The same identify function you use when declaring flags // See https://flags-sdk.dev/docs/api-reference/adapters/statsig#identify-users @@ -247,11 +247,12 @@ export default async function Layout({ const user = await identify({ headers: headersStore, cookies: cookieStore }); // Get a reference to the Statsig SDK instance configured by the adapter - const Statsig = await statsigAdapter.initialize(); + const statsig = await statsigAdapter.initialize(); - // Prepare the bootstrap data on the server, and pass it to the client - const datafile = await Statsig.getClientInitializeResponse(user, { - hash: "djb2", // must use this hash function for compatibility with the client + // Prepare the bootstrap data on the server, and pass it to the client. + // The core SDK returns a JSON string ready to hand to the browser client. + const datafile = statsig.getClientInitializeResponse(new StatsigUser(user), { + hashAlgorithm: "djb2", // must use this hash function for compatibility with the client }); return ( @@ -268,7 +269,6 @@ export default async function Layout({ "use client"; import { useMemo } from "react"; -import type { Statsig } from "@flags-sdk/statsig"; import { StatsigProvider, useClientBootstrapInit, @@ -279,23 +279,26 @@ export function DynamicStatsigProvider({ datafile, }: { children: React.ReactNode; - datafile: Awaited>; + datafile: string; }) { if (!datafile) throw new Error("Missing datafile"); - // Statsig expects a stringified datafile, but ideally the Statsig SDK - // would accept a JSON object so we could avoid this stringification. - const datafileString = useMemo(() => JSON.stringify(datafile), [datafile]); + // The server returns the datafile as a JSON string already, but we still need + // to parse out the user to pass it to the StatsigProvider. + const parsed = useMemo( + () => JSON.parse(datafile) as { user: Record }, + [datafile], + ); const client = useClientBootstrapInit( process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY as string, - datafile.user, - datafileString + parsed.user, + datafile, // NOTE you could provide the Autocapture plugin here ); return ( - + {children} ); @@ -371,21 +374,20 @@ The default Statsig adapter, exported as `statsigAdapter`, will automatically co The Flags SDK automatically initializes the Statsig client when a flag is evaluated. -If you want to initialize the Statsig client before the first flag is used, you can call `statsigAdapter.initialize` manually. Further, -use the manual call to initialize `statsig-node-lite` for usage with other server-side code. +If you want to initialize the Statsig client before the first flag is used, you can call `statsigAdapter.initialize` manually. The returned `Statsig` instance is the same one the adapter uses internally, so you can reuse it for other server-side code. ```ts -import { statsigAdapter, Statsig } from '@flags-sdk/statsig'; +import { statsigAdapter, type StatsigUser } from '@flags-sdk/statsig'; -const statsigInitializationPromise = statsigAdapter.initialize(); +const statsigPromise = statsigAdapter.initialize(); -export async function getStatsigExperiment(key: string) { - await statsigInitializationPromise; - return Statsig.getExperimentSync(key); +export async function getStatsigExperiment(user: StatsigUser, key: string) { + const statsig = await statsigPromise; + return statsig.getExperiment(user, key); } ``` -Use `statsigAdapter.initialize` instead of `Statsig.initialize` as it configures the Statsig client specifically for Flags SDK compatibility. +Use `statsigAdapter.initialize` instead of `new Statsig(...)` directly, as it configures the Statsig client specifically for Flags SDK compatibility (including Edge Config bootstrapping when configured). ### Same key with different mapping functions @@ -416,9 +418,9 @@ export const myDynamicPrice = flag({ }); ``` -### Statsig Node Lite +### Statsig Node Core -The adapter uses `statsig-node-lite`, which is a slimmed version of the Statsig Node.js SDK optimized for server side and Routing Middleware usage. +The adapter uses [`@statsig/statsig-node-core`](https://www.npmjs.com/package/@statsig/statsig-node-core), Statsig's Rust-based server SDK with Node.js bindings. It runs on Node.js (including Vercel's Fluid Compute) and is not compatible with the Edge Runtime. ### Exposure Logging @@ -434,7 +436,7 @@ export const exampleFlag = flag({ }); ``` -When logging is on, your application should also call `Statsig.flush` appropriately to ensure exposures are recorded. +When logging is on, your application should also call `statsig.flushEvents()` (using the instance returned from `statsigAdapter.initialize()`) appropriately to ensure exposures are recorded. The recommended approach for experimentation is to log exposures from the client when the user is indeed exposed to an experiment, either when seen or interacted with. @@ -482,4 +484,4 @@ Read more about Statsig, Flags SDK, and the Statsig adapter. - [Adapter Concept](/docs/adapters/supported-providers) - [Precompute Concept](/principles/precompute) - [Statsig with Next.js](https://docs.statsig.com/client/javascript-sdk/next-js/) -- [Statsig Node Lite on NPM](https://www.npmjs.com/package/statsig-node-lite) +- [Statsig Node Core on NPM](https://www.npmjs.com/package/@statsig/statsig-node-core) diff --git a/packages/adapter-statsig/package.json b/packages/adapter-statsig/package.json index 4563bf50..90063446 100644 --- a/packages/adapter-statsig/package.json +++ b/packages/adapter-statsig/package.json @@ -62,10 +62,8 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@vercel/edge-config": "^1.4.3", - "@vercel/functions": "^1.5.2", - "statsig-node-lite": "^0.5.2", - "statsig-node-vercel": "^0.7.0" + "@statsig/statsig-node-core": "^0.19.3", + "@vercel/edge-config": "^1.4.3" }, "devDependencies": { "@types/node": "20.11.17", diff --git a/packages/adapter-statsig/src/edge-runtime-hooks.ts b/packages/adapter-statsig/src/edge-runtime-hooks.ts deleted file mode 100644 index e65cb03a..00000000 --- a/packages/adapter-statsig/src/edge-runtime-hooks.ts +++ /dev/null @@ -1,73 +0,0 @@ -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 = ( - minSyncDelayMs: number, -): 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; - let isSyncingConfigSpecs = false; - let nextConfigSpecSyncTime = Date.now() + minSyncDelayMs; - return (): void => { - if (Date.now() >= nextConfigSpecSyncTime && !isSyncingConfigSpecs) { - try { - isSyncingConfigSpecs = true; - const sync = Statsig.syncConfigSpecs().finally(() => { - isSyncingConfigSpecs = false; - nextConfigSpecSyncTime = Date.now() + minSyncDelayMs; - }); - import('@vercel/functions').then(({ waitUntil }) => { - waitUntil(sync); - }); - } catch { - // continue - } - } - }; -}; diff --git a/packages/adapter-statsig/src/index.ts b/packages/adapter-statsig/src/index.ts index 2518f39c..5820f1fb 100644 --- a/packages/adapter-statsig/src/index.ts +++ b/packages/adapter-statsig/src/index.ts @@ -1,44 +1,80 @@ export { getProviderData } from './provider'; -import type { Adapter } from 'flags'; -import Statsig, { +import { + type DataStore, type DynamicConfig, + type Experiment, + type FeatureGate, type Layer, + Statsig, type StatsigOptions, type StatsigUser, -} from 'statsig-node-lite'; -import { - createEdgeConfigDataAdapter, - createSyncingHandler, -} from './edge-runtime-hooks'; +} from '@statsig/statsig-node-core'; +import type { Adapter } from 'flags'; -// Export the Statsig instance export { Statsig, type StatsigUser, type StatsigOptions, type DynamicConfig, + type Experiment, + type FeatureGate, type Layer, }; -export type FeatureGate = ReturnType< - typeof Statsig.getFeatureGateWithExposureLoggingDisabledSync ->; - -type AdapterFunction = ( +type AdapterFunction = ( getValue: (obj: O) => T, opts?: { exposureLogging?: boolean }, -) => Adapter; +) => Adapter; type AdapterResponse = { featureGate: AdapterFunction; dynamicConfig: AdapterFunction; - experiment: AdapterFunction; + experiment: AdapterFunction; autotune: AdapterFunction; layer: AdapterFunction; - initialize: () => Promise; + initialize: () => Promise; }; +// Statsig's Vercel integration writes the config-specs payload to Edge Config +// as a parsed JSON object. The Statsig core SDK requests it via DataStore.get +// using keys of the form `statsig|/v2/download_config_specs||` (or +// the legacy `statsig.cache`). We match either, then stringify the object so +// the SDK can parse it. +function isConfigSpecsKey(key: string): boolean { + return ( + key === 'statsig.cache' || + key === '/v1/download_config_specs' || + key === '/v2/download_config_specs' || + /^statsig\|\/v[12]\/download_config_specs\|/.test(key) + ); +} + +async function createEdgeConfigDataStore(options: { + edgeConfigItemKey: string; + edgeConfigConnectionString: string; +}): Promise { + const { createClient } = await import('@vercel/edge-config'); + const client = 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, + }); + + return { + get: async (key) => { + if (!isConfigSpecsKey(key)) return { result: undefined }; + const data = await client.get(options.edgeConfigItemKey); + if (data == null) return { result: undefined }; + const result = typeof data === 'string' ? data : JSON.stringify(data); + return { result }; + }, + supportPollingUpdatesFor: async (key) => isConfigSpecsKey(key), + }; +} + /** * Create a Statsig adapter for use with the Flags SDK. * @@ -58,28 +94,27 @@ export function createStatsigAdapter(options: { itemKey: string; }; }): AdapterResponse { - const initializeStatsig = async (): Promise => { - let dataAdapter: StatsigOptions['dataAdapter'] | undefined; + const initializeStatsig = async (): Promise => { + let dataStore: DataStore | undefined; if (options.edgeConfig) { - dataAdapter = await createEdgeConfigDataAdapter({ + dataStore = await createEdgeConfigDataStore({ 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, + const instance = new Statsig(options.statsigServerApiKey, { + dataStore, // Set a shorter interval during development so developers see changes earlier - rulesetsSyncIntervalMs: + specsSyncIntervalMs: process.env.NODE_ENV === 'development' ? 5_000 : undefined, ...options.statsigOptions, }); + + await instance.initialize(); + return instance; }; - let _initializePromise: Promise | undefined; + let _initializePromise: Promise | undefined; /** * Initialize the Statsig SDK. @@ -89,30 +124,27 @@ export 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 => { + const initialize = async (): Promise => { if (!_initializePromise) { _initializePromise = initializeStatsig(); } - await _initializePromise; - return Statsig; + return _initializePromise; }; const isStatsigUser = (user: unknown): user is StatsigUser => { return user != null && typeof user === 'object'; }; - const minSyncDelayMs = options.edgeConfig ? 1_000 : 5_000; - const syncHandler = createSyncingHandler(minSyncDelayMs); - - async function predecide(user?: StatsigUser): Promise { - await initialize(); - syncHandler?.(); + async function predecide( + user?: StatsigUser, + ): Promise<{ statsig: Statsig; user: StatsigUser }> { + const instance = await initialize(); if (!isStatsigUser(user)) { throw new Error( '@flags-sdk/statsig: Invalid or missing statsigUser from identify. See https://flags-sdk.dev/concepts/identify', ); } - return user; + return { statsig: instance, user }; } function origin(prefix: string) { @@ -130,7 +162,7 @@ export function createStatsigAdapter(options: { /** * Resolve a flag powered by a Feature Gate. * - * Implements `decide` to resolve the Feature Gate with `Statsig.getFeatureGateSync` + * Implements `decide` to resolve the Feature Gate with `Statsig.getFeatureGate`. * * If a function is provided, the return value of the function called * with the feature gate is returned. @@ -147,10 +179,10 @@ export function createStatsigAdapter(options: { 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); + const { statsig, user } = await predecide(entities); + const gate = statsig.getFeatureGate(user, key, { + disableExposureLogging: !opts?.exposureLogging, + }); return getValue(gate); }, }; @@ -159,7 +191,7 @@ export function createStatsigAdapter(options: { /** * Resolve a flag powered by a Dynamic Config. * - * Implements `decide` to resolve the Dynamic Config with `Statsig.getConfigSync` + * Implements `decide` to resolve the Dynamic Config with `Statsig.getDynamicConfig`. * * If a function is provided, the return value of the function called * with the dynamic config is returned. @@ -176,28 +208,28 @@ export function createStatsigAdapter(options: { return { origin: origin('dynamic_configs'), decide: async ({ key, entities }) => { - const user = await predecide(entities); + const { statsig, user } = await predecide(entities); const configKey = key.split('.')[0] ?? ''; - const config = opts?.exposureLogging - ? Statsig.getConfigSync(user, configKey) - : Statsig.getConfigWithExposureLoggingDisabledSync(user, configKey); + const config = statsig.getDynamicConfig(user, configKey, { + disableExposureLogging: !opts?.exposureLogging, + }); return getValue(config); }, }; } function experiment( - getValue: (experiment: DynamicConfig) => T, + getValue: (experiment: Experiment) => T, opts?: { exposureLogging?: boolean }, ): Adapter { 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); + const { statsig, user } = await predecide(entities); + const result = statsig.getExperiment(user, key, { + disableExposureLogging: !opts?.exposureLogging, + }); + return getValue(result); }, }; } @@ -209,11 +241,11 @@ export function createStatsigAdapter(options: { 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); + const { statsig, user } = await predecide(entities); + const result = statsig.getDynamicConfig(user, key, { + disableExposureLogging: !opts?.exposureLogging, + }); + return getValue(result); }, }; } @@ -225,11 +257,11 @@ export function createStatsigAdapter(options: { 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); + const { statsig, user } = await predecide(entities); + const result = statsig.getLayer(user, key, { + disableExposureLogging: !opts?.exposureLogging, + }); + return getValue(result); }, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 047723f1..421c0708 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -792,18 +792,12 @@ importers: packages/adapter-statsig: dependencies: + '@statsig/statsig-node-core': + specifier: ^0.19.3 + version: 0.19.3 '@vercel/edge-config': specifier: ^1.4.3 version: 1.4.3(@opentelemetry/api@1.9.0)(next@16.2.0(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(react-dom@19.2.4(react@19.3.0-canary-00f063c3-20260415))(react@19.3.0-canary-00f063c3-20260415)) - '@vercel/functions': - specifier: ^1.5.2 - version: 1.6.0 - statsig-node-lite: - specifier: ^0.5.2 - version: 0.5.2 - statsig-node-vercel: - specifier: ^0.7.0 - version: 0.7.0(@vercel/edge-config@1.4.3(@opentelemetry/api@1.9.0)(next@16.2.0(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(react-dom@19.2.4(react@19.3.0-canary-00f063c3-20260415))(react@19.3.0-canary-00f063c3-20260415))) devDependencies: '@types/node': specifier: 20.11.17 @@ -2736,6 +2730,36 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@octokit/auth-token@5.1.2': + resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.6': + resolution: {integrity: sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.4': + resolution: {integrity: sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.2.2': + resolution: {integrity: sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@25.1.0': + resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + + '@octokit/request-error@6.1.8': + resolution: {integrity: sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==} + engines: {node: '>= 18'} + + '@octokit/request@9.2.4': + resolution: {integrity: sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==} + engines: {node: '>= 18'} + + '@octokit/types@14.1.0': + resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -3992,6 +4016,58 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@statsig/statsig-node-core-darwin-arm64@0.19.3': + resolution: {integrity: sha512-B0ck7PkA3IXmGUAZLlZOORI/fR4SPlXdwRyRlwQ//Km/pZ1IlDyXzc90UPXkf6uzOCjc/uuDsN3yDpSb7NNMzQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@statsig/statsig-node-core-darwin-x64@0.19.3': + resolution: {integrity: sha512-nKy6r9AqVKuvs/b8oDsuuWS3gycNct2grERZdfSE2557ZIPk69948SmAojWGXPminzaWxcZb+W2aQgITzwr6kg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@statsig/statsig-node-core-linux-arm64-gnu@0.19.3': + resolution: {integrity: sha512-wMX4DXAX2jdrhFaOkiPqMyMkD/koQKdeDmf32Ep4ztkPXVFp5LlvNVwxQlJYN9dB/RmYGfrxKHLW3tR5wRr6NQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@statsig/statsig-node-core-linux-arm64-musl@0.19.3': + resolution: {integrity: sha512-2aV9UpyksT14UBRDTp0Yw4otSkGb/K7abR+1steY1TtmzQOczxxkkV3kmhzto8R5MzsOFtsvgkdgkEzwBgKJmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@statsig/statsig-node-core-linux-x64-gnu@0.19.3': + resolution: {integrity: sha512-LpuoKpRyyoGo8gB7HubTbi5au+p7bIhWw3JZfGe6ea/25RjTHmaKo7C9VDSbedwvUlP2VW7YtBsGDMco1r+iAA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@statsig/statsig-node-core-linux-x64-musl@0.19.3': + resolution: {integrity: sha512-flD4hod+XK2JB90VgRNhxtl20hwNEI62/kOUoAXsO5wyLQGdW80hib8PmFS2IKDonJpFY0Wh7G9UW2cpv+X67g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@statsig/statsig-node-core-win32-ia32-msvc@0.19.3': + resolution: {integrity: sha512-CZOzJ7tQBWiR3ErBM7VQyIWT4aTdUV/yLhdGkJxasb0tYaUop1cK+TB03R0nhOkgX7fVdPVp0aimwbKayP2+OQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@statsig/statsig-node-core-win32-x64-msvc@0.19.3': + resolution: {integrity: sha512-+BXcNKUvquGjXh4bHAhP/wGXW8kCE2Mzo+5/3bYf2+WuwPHEegX9EcmdohqNGGrttKTZGrVyu1alOrJZaYz78g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@statsig/statsig-node-core@0.19.3': + resolution: {integrity: sha512-dzrG/NSaqg7lf8TgUNgivkbQqQvtfB2PMIIqJ4neHzn5Hb8iU6Zwd0iSkr5W+sEweXtV1Hj4LPtm2uaR0vIxGQ==} + engines: {node: '>= 6.14.2 < 7 || >= 8.11.2 < 9 || >= 9.11.0 < 10 || >= 10.0.0'} + '@streamdown/cjk@1.0.2': resolution: {integrity: sha512-5OOuZjj2Lnae92Zmg2gA5hloSbcKj25gv+QY4iKbYI+iRsiGWbgmYxmgxNUSO9SR6BKOCy783UHN1HM/QEUpdw==} peerDependencies: @@ -5242,6 +5318,9 @@ packages: bcp-47@2.1.0: resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -6178,6 +6257,9 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -8622,17 +8704,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statsig-node-lite@0.4.4: - resolution: {integrity: sha512-2jpkawGAEf2OS5mjay0x4JxdVqrhu7FbHx8dcjvo20aA2uaKyqsCyUn4TvFRgUmNpXmZ+OaNptda7ehs4vdZFg==} - - statsig-node-lite@0.5.2: - resolution: {integrity: sha512-3yndSBNymOGHWZw9RlkCd4pQ1ZP4LsZ6Goy6yFO+bplv0AssnXZq3y2EljtAqPrx/Hi7F2rwewJMTHDM/B2wng==} - - statsig-node-vercel@0.7.0: - resolution: {integrity: sha512-TNcJm2yZep6qdPE4A6FFQgSZRDNasp50oUXH96feBTUUYVmZl6NryQ0Gy9NXWmUi5e29IYWNansD4WSVVYi7XA==} - peerDependencies: - '@vercel/edge-config': ^1.0.0 - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -9093,6 +9164,9 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -9153,10 +9227,6 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - validate-npm-package-name@5.0.1: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -10850,6 +10920,47 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@octokit/auth-token@5.1.2': {} + + '@octokit/core@6.1.6': + dependencies: + '@octokit/auth-token': 5.1.2 + '@octokit/graphql': 8.2.2 + '@octokit/request': 9.2.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@10.1.4': + dependencies: + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@8.2.2': + dependencies: + '@octokit/request': 9.2.4 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@25.1.0': {} + + '@octokit/request-error@6.1.8': + dependencies: + '@octokit/types': 14.1.0 + + '@octokit/request@9.2.4': + dependencies: + '@octokit/endpoint': 10.1.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + fast-content-type-parse: 2.0.1 + universal-user-agent: 7.0.3 + + '@octokit/types@14.1.0': + dependencies: + '@octokit/openapi-types': 25.1.0 + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': @@ -12230,6 +12341,48 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@statsig/statsig-node-core-darwin-arm64@0.19.3': + optional: true + + '@statsig/statsig-node-core-darwin-x64@0.19.3': + optional: true + + '@statsig/statsig-node-core-linux-arm64-gnu@0.19.3': + optional: true + + '@statsig/statsig-node-core-linux-arm64-musl@0.19.3': + optional: true + + '@statsig/statsig-node-core-linux-x64-gnu@0.19.3': + optional: true + + '@statsig/statsig-node-core-linux-x64-musl@0.19.3': + optional: true + + '@statsig/statsig-node-core-win32-ia32-msvc@0.19.3': + optional: true + + '@statsig/statsig-node-core-win32-x64-msvc@0.19.3': + optional: true + + '@statsig/statsig-node-core@0.19.3': + dependencies: + '@octokit/core': 6.1.6 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0 + optionalDependencies: + '@statsig/statsig-node-core-darwin-arm64': 0.19.3 + '@statsig/statsig-node-core-darwin-x64': 0.19.3 + '@statsig/statsig-node-core-linux-arm64-gnu': 0.19.3 + '@statsig/statsig-node-core-linux-arm64-musl': 0.19.3 + '@statsig/statsig-node-core-linux-x64-gnu': 0.19.3 + '@statsig/statsig-node-core-linux-x64-musl': 0.19.3 + '@statsig/statsig-node-core-win32-ia32-msvc': 0.19.3 + '@statsig/statsig-node-core-win32-x64-msvc': 0.19.3 + transitivePeerDependencies: + - encoding + - supports-color + '@streamdown/cjk@1.0.2(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.4)(unified@11.0.5)': dependencies: react: 19.2.4 @@ -13793,6 +13946,8 @@ snapshots: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 + before-after-hook@3.0.2: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -14739,7 +14894,7 @@ snapshots: '@next/eslint-plugin-next': 16.2.0 eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.38.0(jiti@2.6.1)) @@ -14762,7 +14917,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -14777,14 +14932,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -14799,7 +14954,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15024,6 +15179,8 @@ snapshots: extendable-error@0.1.7: {} + fast-content-type-parse@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -18098,29 +18255,6 @@ snapshots: stackback@0.0.2: {} - statsig-node-lite@0.4.4: - dependencies: - node-fetch: 2.7.0 - ua-parser-js: 1.0.41 - uuid: 8.3.2 - transitivePeerDependencies: - - encoding - - statsig-node-lite@0.5.2: - dependencies: - node-fetch: 2.7.0 - ua-parser-js: 1.0.41 - uuid: 8.3.2 - transitivePeerDependencies: - - encoding - - statsig-node-vercel@0.7.0(@vercel/edge-config@1.4.3(@opentelemetry/api@1.9.0)(next@16.2.0(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(react-dom@19.2.4(react@19.3.0-canary-00f063c3-20260415))(react@19.3.0-canary-00f063c3-20260415))): - dependencies: - '@vercel/edge-config': 1.4.3(@opentelemetry/api@1.9.0)(next@16.2.0(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(react-dom@19.2.4(react@19.3.0-canary-00f063c3-20260415))(react@19.3.0-canary-00f063c3-20260415)) - statsig-node-lite: 0.4.4 - transitivePeerDependencies: - - encoding - statuses@2.0.2: {} std-env@3.10.0: {} @@ -18700,6 +18834,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} + universalify@0.1.2: {} universalify@0.2.0: {} @@ -18789,8 +18925,6 @@ snapshots: uuid@11.1.0: {} - uuid@8.3.2: {} - validate-npm-package-name@5.0.1: {} vaul@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):