UNPKG

hypertune

Version:

[Hypertune](https://www.hypertune.com/) is the most flexible platform for feature flags, A/B testing, analytics and app configuration. Built with full end-to-end type-safety, Git-style version control and local, synchronous, in-memory flag evaluation. Opt

1,181 lines (1,018 loc) 34.1 kB
/* eslint-disable unused-imports/no-unused-vars */ /* eslint-disable capitalized-comments */ import { isQueryVariableKey } from "./constants"; /** * It's difficult to remove fields from server-returned types (Expression, * Split, EventType, CommitConfig, etc) as old SDK versions will depend on them. * So we should only add fields to these types if we don't expect to remove them * later. */ // Value Type export type ValueType = | VoidValueType | BooleanValueType | IntValueType | FloatValueType | StringValueType | RegexValueType | EnumValueType | ObjectValueType | UnionValueType | ListValueType | FunctionValueType; export type VoidValueType = { type: "VoidValueType" }; export type BooleanValueType = { type: "BooleanValueType" }; export type IntValueType = { type: "IntValueType" }; export type FloatValueType = { type: "FloatValueType" }; export type StringValueType = { type: "StringValueType" }; export type RegexValueType = { type: "RegexValueType" }; export type EnumValueType = { type: "EnumValueType"; enumTypeName: string; // Must be an enum type defined in the schema }; export type ObjectValueType = { type: "ObjectValueType"; objectTypeName: string; // Must be an object type defined in the schema }; export type UnionValueType = { type: "UnionValueType"; unionTypeName: string; // Must be a union type defined in the schema }; export type ListValueType = { type: "ListValueType"; itemValueType: ValueType; }; export type FunctionValueType = { type: "FunctionValueType"; parameterValueTypes: ValueType[]; returnValueType: ValueType; }; // Schema export type Schema = { enums: { [enumTypeName: string]: EnumSchema }; objects: { [objectTypeName: string]: ObjectSchema }; unions: { [unionTypeName: string]: UnionSchema }; tags?: { [name: string]: Tag }; }; export type CommonTypeSchema = { description: string | null; }; export type DeprecationTypeSchema = { deprecationReason?: string; }; export type EnumSchema = CommonTypeSchema & { values: { [enumValue: string]: EnumValueSchema }; }; export type EnumValueSchema = CommonTypeSchema & DeprecationTypeSchema; export type ObjectRole = "args" | "input" | "output" | "event"; export type ObjectSchema = CommonTypeSchema & { role: ObjectRole; fields: { [fieldName: string]: ObjectFieldSchema }; }; export type Tag = { name: string; color: string; }; export type ObjectFieldSchema = CommonTypeSchema & DeprecationTypeSchema & { valueType: ValueType; }; export type UnionSchema = CommonTypeSchema & { variants: { [objectTypeName: string]: true }; }; // Expression export type Expression = | NoOpExpression | BooleanExpression | IntExpression | FloatExpression | StringExpression | RegexExpression | EnumExpression | ObjectExpression | GetFieldExpression | UpdateObjectExpression | ListExpression | SwitchExpression | EnumSwitchExpression | ComparisonExpression | ArithmeticExpression | RoundNumberExpression | StringifyNumberExpression | StringConcatExpression // TODO: Break into primitives | GetUrlQueryParameterExpression // TODO: Break into primitives | SplitExpression // TODO: Break into primitives | LogEventExpression | FunctionExpression | VariableExpression | ApplicationExpression; export type BaseExpressionFields = { id: string; // Nano ID isTransient?: boolean; logs?: Logs; metadata?: { note?: string; permissions?: Permissions; tags?: { [tagName: string]: true }; }; }; export type LogsHandler = ( logs: Required< Pick<Logs, "messageList" | "eventList" | "evaluationList" | "exposureList"> > ) => void; export type Logs = { messageList?: Message[]; eventList?: Event[]; exposureList?: Exposure[]; evaluationList?: Evaluation[]; evaluations?: CountMap; // Key: expressionId /** @deprecated - use eventList instead */ events?: CountMap; // Key: stableStringifiedEvent /** @deprecated - use exposureList instead */ exposures?: CountMap; // Key: stableStringifiedExposure }; export type CountMap = { [key: string]: number }; export type Evaluation = { path: string; value: Value; args: { [path: string]: ObjectValue }; isFallback: boolean; }; export type Exposure = { splitId: string; unitId: string; event: Event | null; assignment: SplitAssignment; }; export type Event = { objectTypeName: string; payload: ObjectValue; }; export type Message = { level: LogLevel; message: string; metadata: object; }; export type Permissions = { // TODO: Maybe add inherit flag to inherit permissions from the parent group: { [groupId: string]: Permission }; user: { [userId: string]: Permission }; }; export type Permission = { // TODO: read write: "allow"; // TODO: unset, deny }; export type NoOpExpression = BaseExpressionFields & { type: typeof NoOpExpressionType; valueType: VoidValueType; }; export const NoOpExpressionType = "NoOpExpression" as const; export type BooleanExpression = BaseExpressionFields & { type: typeof BooleanExpressionType; valueType: BooleanValueType; value: boolean; }; export const BooleanExpressionType = "BooleanExpression" as const; export type IntExpression = BaseExpressionFields & { type: typeof IntExpressionType; valueType: IntValueType; value: number; }; export const IntExpressionType = "IntExpression" as const; export type FloatExpression = BaseExpressionFields & { type: typeof FloatExpressionType; valueType: FloatValueType; value: number; }; export const FloatExpressionType = "FloatExpression" as const; export type StringExpression = BaseExpressionFields & { type: typeof StringExpressionType; valueType: StringValueType; value: string; }; export const StringExpressionType = "StringExpression" as const; export type RegexExpression = BaseExpressionFields & { type: typeof RegexExpressionType; valueType: RegexValueType; value: string; // Must be a valid regex }; export const RegexExpressionType = "RegexExpression" as const; export type EnumExpression = BaseExpressionFields & { type: typeof EnumExpressionType; valueType: EnumValueType; // E.g. Language value: string; // Must be a valid enum value of the enum given by valueType }; export const EnumExpressionType = "EnumExpression" as const; export type ObjectExpression = BaseExpressionFields & { type: typeof ObjectExpressionType; valueType: ObjectValueType; // E.g. Content // Must match valueType.objectTypeName; added explicitly so SDK code doesn't // depend on valueType and we can remove it in future, e.g. if we move from // explicit type annotations to type inference objectTypeName: string; // Must contain the field names of all the fields in the schema object // objectTypeName. Each field's valueType must match the valueType // given by its matching schema field fields: { [fieldName: string]: Expression | null }; }; export const ObjectExpressionType = "ObjectExpression" as const; export type GetFieldExpression = BaseExpressionFields & { type: typeof GetFieldExpressionType; valueType: ValueType; // E.g. String object: Expression | null; // Must have an ObjectValueType valueType // Must be a valid field path in the schema object referenced by object's // valueType and the valueType of the field must match this expression's // valueType fieldPath: string | null; }; export const GetFieldExpressionType = "GetFieldExpression" as const; export type UpdateObjectExpression = BaseExpressionFields & { type: typeof UpdateObjectExpressionType; valueType: ObjectValueType; // E.g. Content // Its valueType must match this expression's valueType object: Expression | null; // Each field name must be a valid one in the schema object referenced by // valueType.objectValueType and the valueType of its expression must match // the valueType of the referenced field updates: { [fieldName: string]: Expression | null }; }; export const UpdateObjectExpressionType = "UpdateObjectExpression" as const; export type ListExpression = BaseExpressionFields & { type: typeof ListExpressionType; valueType: ListValueType; // E.g. List[BlogPost] // Each item's valueType must match valueType.itemValueType items: (Expression | null)[]; }; export const ListExpressionType = "ListExpression"; export type SwitchExpression = BaseExpressionFields & { type: typeof SwitchExpressionType; valueType: ValueType; // E.g. Content control: Expression | null; cases: { id: string; // Its valueType must match control's valueType when: Expression | null; // Its valueType must match this expression's valueType then: Expression | null; }[]; // Its valueType must match this expression's valueType default: Expression | null; }; export const SwitchExpressionType = "SwitchExpression" as const; export type EnumSwitchExpression = BaseExpressionFields & { type: typeof EnumSwitchExpressionType; valueType: ValueType; // E.g. Content control: Expression | null; // Must have an EnumValueType valueType // Must contain the enum values of all the enum values in the schema enum // matching control.valueType. Each expression's valueType must match this // expression's valueType cases: { [enumValue: string]: Expression | null }; }; export const EnumSwitchExpressionType = "EnumSwitchExpression" as const; const comparisonOperators = [ "==", "!=", "<", "<=", ">", ">=", "AND", "OR", "in", "notIn", "startsWith", "notStartsWith", "endsWith", "notEndsWith", "contains", "notContains", "matches", "notMatches", ] as const; export type ComparisonOperator = (typeof comparisonOperators)[number]; export type ComparisonExpression = BaseExpressionFields & { type: typeof ComparisonExpressionType; valueType: BooleanValueType; operator: ComparisonOperator | null; a: Expression | null; // Must be compatible with operator and b b: Expression | null; // Must be compatible with operator and a }; export const ComparisonExpressionType = "ComparisonExpression"; export const arithmeticOperators = ["+", "-", "*", "/", "POW", "MOD"] as const; export type ArithmeticOperator = (typeof arithmeticOperators)[number]; export type ArithmeticExpression = BaseExpressionFields & { type: typeof ArithmeticExpressionType; valueType: IntValueType | FloatValueType; operator: ArithmeticOperator | null; // The a and b expressions can only have an IntValueType valueType if this // expression does else they can have a FloatValueType valueType too a: Expression | null; b: Expression | null; }; export const ArithmeticExpressionType = "ArithmeticExpression" as const; export type RoundNumberExpression = BaseExpressionFields & { type: typeof RoundNumberExpressionType; valueType: IntValueType; // Must have a FloatValueType (or IntValueType) valueType number: Expression | null; }; export const RoundNumberExpressionType = "RoundNumberExpression" as const; export type StringifyNumberExpression = BaseExpressionFields & { type: typeof StringifyNumberExpressionType; valueType: StringValueType; // Must have a FloatValueType (or IntValueType) valueType number: Expression | null; }; export const StringifyNumberExpressionType = "StringifyNumberExpression" as const; export type StringConcatExpression = BaseExpressionFields & { type: typeof StringConcatExpressionType; valueType: StringValueType; strings: Expression | null; // Must have a List[String] valueType }; export const StringConcatExpressionType = "StringConcatExpression" as const; export type GetUrlQueryParameterExpression = BaseExpressionFields & { type: typeof GetUrlQueryParameterExpressionType; valueType: StringValueType; url: Expression | null; // Must have a String valueType queryParameterName: Expression | null; // Must have a String valueType }; export const GetUrlQueryParameterExpressionType = "GetUrlQueryParameterExpression" as const; // Evaluating this expression will log an exposure for the given <unitId, armId> // if !!expose export type SplitExpression = BaseExpressionFields & { type: typeof SplitExpressionType; valueType: ValueType; // E.g. Content splitId: string | null; // Must be a valid split ID dimensionId: string | null; // Must be a dimension ID in the selected split expose: Expression | null; // Must have a Boolean valueType unitId: Expression | null; // Must have a String valueType // The dimension mapping type must match the dimension type dimensionMapping: DimensionMapping; eventObjectTypeName: string | null; // Must match the object value type name of the payload. eventPayload: Expression | null; // Must have Object valueType // @deprecated - use payload instead. featuresMapping: FeaturesMapping; }; export const SplitExpressionType = "SplitExpression" as const; export type DimensionMapping = | DiscreteDimensionMapping | ContinuousDimensionMapping; export type DiscreteDimensionMapping = { type: typeof DiscreteDimensionType; // Must contain all the arm IDs in the selected (discrete) dimension as well // as a default arm if needed and each arm expression's valueType must match // this expression's valueType cases: { [armId: string]: Expression | null }; }; export const DiscreteDimensionType = "discrete"; export type FeaturesMapping = { [featureId: string]: Expression | null }; export type ContinuousDimensionMapping = { type: typeof ContinuousDimensionType; // Must have a FunctionValueType valueType with a single Float parameter // type and a return type that matches this expression's valueType function: Expression | null; }; export const ContinuousDimensionType = "continuous"; // Evaluating this expression will log an event <eventTypeId, unitId> export type LogEventExpression = BaseExpressionFields & { type: typeof LogEventExpressionType; valueType: VoidValueType; eventObjectTypeName: string | null; // Must match the object value type name of the payload. eventPayload: Expression | null; // Must have Object valueType // @deprecated - use payload instead. eventTypeId: string | null; // Must be a valid event type ID // @deprecated - use payload instead. unitId: Expression | null; // Must have a String valueType // @deprecated - use payload instead. featuresMapping: FeaturesMapping; }; export const LogEventExpressionType = "LogEventExpression" as const; export type FunctionExpression = BaseExpressionFields & { type: typeof FunctionExpressionType; valueType: FunctionValueType; // E.g. (Int, Int) -> Int // Must be of length valueType.parameterValueTypes.length parameters: Parameter[]; // This expression and its descendants can reference the parameters as // variables. Its valueType must match the returnValueType of this // expression's valueType body: Expression | null; }; export const FunctionExpressionType = "FunctionExpression" as const; export type Parameter = { id: string; // Nano ID name: string; }; export type VariableExpression = BaseExpressionFields & { type: typeof VariableExpressionType; valueType: ValueType; // The valueType of the variable must match this expression's valueType variableId: string; }; export const VariableExpressionType = "VariableExpression" as const; export type ApplicationExpression = BaseExpressionFields & { type: typeof ApplicationExpressionType; valueType: ValueType; // Must have a FunctionValueType valueType with a returnValueType that matches // this expression's valueType function: Expression | null; // Must contain an argument for each function parameter and each argument's // valueType must match its parameter's valueType arguments: (Expression | null)[]; }; export const ApplicationExpressionType = "ApplicationExpression" as const; // Split export type SplitType = "test" | "ml"; export type SplitBase = { id: string; // Nano ID name: string; description?: string; archived?: boolean; dimensions: { [dimensionId: string]: Dimension }; eventObjectTypeName: string | null; // refers to event object schema definition // @deprecated - use payload instead featureIds: { [featureId: string]: true }; }; export type TestSplit = SplitBase & { type: "test"; }; export type MLSplit = SplitBase & { type: "ml"; rewardEvents: { eventObjectTypeName: string; // refers to event object schema definition unitIdPayloadPath: string[]; // path to unit id in the payload of the goal event type }[]; // E.g. LEAST(COUNT(*) FILTER (WHERE event_object_type_name = 'ClickEvent'), 1) // TODO: Develop abstraction over raw SQL rewardSql: string; }; export type Split = TestSplit | MLSplit; export type SplitMap = { [splitId: string]: Split }; export type Dimension = DiscreteDimension | ContinuousDimension; type BaseDimensionFields = { id: string; // Nano ID splitId: string; index: number; name: string; }; export type DiscreteDimension = BaseDimensionFields & { type: typeof DiscreteDimensionType; arms: { [armId: string]: Arm }; controlArmId?: string; }; export type Arm = { id: string; // Nano ID dimensionId: string; index: number; name: string; allocation: number; // E.g. 0.5; only used for test splits description?: string; }; type ContinuousDimension = BaseDimensionFields & { type: typeof ContinuousDimensionType; range: number[][]; // E.g. [ [-10, -5), [0, 1.3) , [1.5, 2) ] }; // Event Type // @deprecated - use object with role event instead export type EventType = { id: string; // Nano ID name: string; // @deprecated - use payload instead featureIds: { [featureId: string]: true }; }; // @deprecated export type EventTypeMap = { [eventTypeId: string]: EventType }; // Feature // @deprecated - use event and split payloads instead export type Feature = { id: string; // Nano ID name: string; valueType: ValueType; }; // @deprecated export type FeatureMap = { [featureId: string]: Feature }; // Commit export type BaseCommitData = { schemaCode: string; expression: Expression; splits: SplitMap; eventTypes: EventTypeMap; features: FeatureMap; }; export type CommitData = BaseCommitData & { id: number; projectId: number; config: CommitConfig; hash: string; }; export type ActiveCommitData = CommitData; // Commit Config export type CommitConfig = { splitConfig: { [splitId: string]: SplitConfig }; }; export type SplitConfig = EpsilonGreedyConfig | PersonalizationSplitConfig; export type EpsilonGreedyConfig = { type: "EpsilonGreedyConfig"; epsilon: number; // Between 0 and 1 bestAssignment: SplitAssignment; }; type PersonalizationSplitConfig = { type: "PersonalizationSplitConfig"; epsilon: number; // Between 0 and 1 logic: { [dimensionId: string]: { rules: { featureValuesPath: string[]; featureValue: Value; armId: string; }[]; defaultArmId: string; }; }; }; // Query export type Query< TFieldArguments extends ObjectValueWithVariables | ObjectExpression, > = { name?: string; variableDefinitions: VariableDefinitions; fragmentDefinitions: FragmentDefinitions<TFieldArguments>; fieldQuery: FieldQuery<TFieldArguments>; }; export type FragmentDefinitions< TFieldArguments extends ObjectValueWithVariables | ObjectExpression, > = { [fragmentName: string]: InlineFragment<TFieldArguments>; }; export type VariableDefinitions = { [variableName: string]: { valueType: ValueType; defaultValue?: Value }; }; export type FieldQuery< TFieldArguments extends ObjectValueWithVariables | ObjectExpression, > = { [objectTypeName: string]: Fragment<TFieldArguments>; }; export type Fragment< TFieldArguments extends ObjectValueWithVariables | ObjectExpression, > = InlineFragment<TFieldArguments> | FragmentSpread; export type InlineFragment< TFieldArguments extends ObjectValueWithVariables | ObjectExpression, > = { type: "InlineFragment"; objectTypeName: string; selection: Selection<TFieldArguments>; }; export type FragmentSpread = { type: "FragmentSpread"; fragmentName: string; }; export type Selection< TFieldArguments extends ObjectValueWithVariables | ObjectExpression, > = { [fieldName: string]: { fieldArguments: TFieldArguments; fieldQuery: FieldQuery<TFieldArguments> | null; }; }; // Value export type Value = boolean | number | string | ObjectValue | Value[]; // Any fieldName starting with __ is reserved for internal use export type ObjectValue = { [fieldName: string]: Value }; export type QueryVariable = { __isVariable: true; name: string }; export function isQueryVariable( value: ValueWithVariables ): value is QueryVariable { return ( typeof value === "object" && value !== null && isQueryVariableKey in value ); } export type ValueWithVariables = | Value | QueryVariable | ValueWithVariables[] | ObjectValueWithVariables; export type ObjectValueWithVariables = { [fieldName: string]: ValueWithVariables; }; export type SplitAssignment = { [dimensionId: string]: SplitAssignmentEntry; }; export type SplitAssignmentEntry = | { type: typeof DiscreteDimensionType; armId: string } | { type: typeof ContinuousDimensionType; value: number }; export type SplitAssignmentWithNullableEntries = { [dimensionId: string]: SplitAssignmentEntry | null; }; export const requestTypes = [ "codegen", "graphql", "init", "hash", "js", "schema", ] as const; export type RequestType = (typeof requestTypes)[number]; // Edge Types export type SdkType = "js" | "python" | "rust"; export type JsLanguage = "ts" | "js" | "mjs" | "cjs"; export type Language = JsLanguage | "python" | "rust"; export type CodegenFramework = | "nextApp" | "nextPages" | "react" | "reactNative" | "remix" | "gatsby" | "vue"; export type CodegenPlatform = "vercel"; export type CodegenRequestBody = { query: string | null; framework?: CodegenFramework; platform?: CodegenPlatform; clientFileName?: string; getHypertuneImportPath?: string; includeToken?: boolean; includeFallback?: boolean; includeToolbar?: boolean; sdkType: SdkType; sdkVersion: string; language: Language; }; // QueryObjectValueWithVariables is a concrete instance of // Query<ObjectValueWithVariables> for compatibility with Zod export type QueryObjectValueWithVariables = { name?: string; variableDefinitions: VariableDefinitions; fragmentDefinitions: { [fragmentName: string]: InlineFragmentObjectValueWithVariables; }; fieldQuery: FieldQueryObjectValueWithVariables; }; export type FieldQueryObjectValueWithVariables = { [objectTypeName: string]: FragmentObjectValueWithVariables; }; export type FragmentObjectValueWithVariables = | InlineFragmentObjectValueWithVariables | FragmentSpread; export type InlineFragmentObjectValueWithVariables = { type: "InlineFragment"; objectTypeName: string; selection: SelectionObjectValueWithVariables; }; export type SelectionObjectValueWithVariables = { [fieldName: string]: { fieldArguments: ObjectValueWithVariables; fieldQuery: FieldQueryObjectValueWithVariables | null; }; }; // Compiler check to ensure the two types remain compatible. const typedQueryToGeneric: Query<ObjectValueWithVariables> = {} as QueryObjectValueWithVariables; const typedGenericQueryToTyped: QueryObjectValueWithVariables = {} as Query<ObjectValueWithVariables>; export type InitQuery = | { type: "Query"; query: QueryObjectValueWithVariables | null } | { type: "GraphqlQuery"; code: string } | { type: "StoredQuery"; id: string }; export type InitRequestBody = { // TODO: Remove support for `string` when everyone is using SDK version >= 2.4 query: string | InitQuery; variables: ObjectValue; sdkType: SdkType; sdkVersion: string; }; export type GraphqlRequestBody = { query: string; variables?: ObjectValue; schemaVersion?: string; }; export type SchemaRequestBody = { schemaVersion?: string; optionalInputTypes?: boolean; introspection?: boolean; }; export type CodegenFile = { name: string; content: string; }; export type CodegenResponseBody = { files: CodegenFile[]; messages: Message[]; /** @deprecated */ code?: string; /** @deprecated */ frameworkCode?: string; }; export type InitData = { commitId: number; hash: string; reducedExpression: Expression; splits: SplitMap; commitConfig: CommitConfig; }; export type HashData = { commitId: number; hash: string; }; // SDK export type Step = GetFieldStep | GetItemStep; type GetFieldStep = { type: "GetFieldStep"; fieldName: string; fieldArguments: ObjectValue; }; type GetItemStep = { type: "GetItemStep"; index: number; fallbackLength: number; }; export type UpdateTrigger = "initDataProvider" | "hydration" | "override"; export type UpdateListener = ( newStateHash: string, // TODO: move into metadata in the next major release. metadata: { becameReady: boolean; updateTrigger: UpdateTrigger; hasUpdated: boolean; } ) => void; export type TracedFetch = ( traceId: string, url: string, requestInit: Omit<RequestInit, "headers"> & { headers: Record<string, string>; } ) => Promise<Response>; export type InitDataProvider = { getName: () => string; getInitData: GetInitDataFunction; getHashData?: GetHashDataFunction; }; export type GetInitDataFunction = (args: { traceId: string; initQuery: InitQuery; variableValues: ObjectValue; }) => Promise<InitData>; export type GetHashDataFunction = (args: { traceId: string; initQuery: InitQuery; variableValues: ObjectValue; }) => Promise<HashData>; /** * `CreateOptions` contains options used to create the Hypertune source. Options * with generated types are defined in your generated code. */ export type CreateOptions = { /** * `branchName` specifies a Hypertune branch to initialize from. * This is useful for testing significant logic and schema changes, especially * breaking schema changes, as different schema versions can be served at the * same time on different branches during a migration. */ branchName?: string; /** * `initData` specifies initial `InitData` for the SDK, allowing it to be used * immediately. This can be supplied via a static build-time snapshot in your * generated client, via hydration or even manually (e.g. in unit tests). */ initData?: InitData; /** * `initDataProvider` specifies where the SDK should fetch its data from. * * - When set to `null` the SDK won't fetch any initialization data, relying * only on a build-time snapshot in your generated client or hydration. * - When set to `undefined` it will default to * `HypertuneEdgeInitDataProvider`. * - For initialization from Vercel Edge Config, set this to a new instance of * `VercelEdgeConfigInitDataProvider`. * * @default HypertuneEdgeInitDataProvider */ initDataProvider?: InitDataProvider | null; /** * `lastInitDataRefreshTime` is a timestamp specifying the last time * the provided `initData` was last checked for freshness using an * `InitDataProvider`. It is ignored when no `initData` was provided. */ lastInitDataRefreshTime?: number | null; /** * `initDataRefreshIntervalMs` specifies the interval, in milliseconds, at * which the SDK checks for updates. The minimum allowed value is 1000ms. * * - When `initIfNeeded` is called, the SDK will make an initialization * request only if the last initialization occurred at least this interval * ago. * * - When `shouldRefreshInitData` is `true`, this value controls how often the * SDK checks for updates in the background. * * - When `initDataProvider` is null, the SDK will never check for updates. * * @default 2000 */ initDataRefreshIntervalMs?: number; /** * `shouldRefreshInitData` specifies whether the SDK checks for flag updates * in the background. The frequency of checks is controlled by the * `initDataRefreshIntervalMs` option. * * When `initDataProvider` is `null` the SDK will never check for updates. * * @default true (false when using VercelEdgeConfigInitDataProvider) */ shouldRefreshInitData?: boolean; /** * `shouldRefreshInitDataOnCreate` specifies whether the SDK should * initialize from `initDataProvider` on creation. * * When `initDataProvider` is `null` the SDK will never check for updates. * * @default true (false when using VercelEdgeConfigInitDataProvider) */ shouldRefreshInitDataOnCreate?: boolean; /** * shouldSkipInitDataUpdateOnRefresh controls whether the SDK skips fetching * and updating its internal state when it detects a new commit exists. It * still triggers an update notification. This is useful for clients that * hydrate from their own servers. * * @default false */ shouldSkipInitDataUpdateOnRefresh?: boolean; /** * cacheSize controls the size of internal SDK caches. * * Setting it to 0 disables caching. * * @default 250 */ cacheSize?: number; remoteLogging?: { /** * `mode` determines how log messages, expression evaluations, split * exposures and analytics events are sent to the remote server. * * - When set to "normal", all data is sent to the remote server. * * - When set to "off", no data is sent to the remote server. This is useful * in development and test environments. * * - When set to "session", log messages, expression evaluations and * split exposures are deduplicated per session, based on the provided * context. However, all analytics events are still sent to the remote * server. * * @default "normal" ("session" when the SDK is used in the browser) */ mode?: RemoteLoggingMode; /** * `endpointUrl` allows you to send SDK log messages, expression * evaluations, split exposures and analytics events to your own server. You * can then forward them to Hypertune Edge or other analytics or * observability systems. * * @default https://gcp.fasthorse.workers.dev/logs */ endpointUrl?: string; /** * `flushIntervalMs` specifies the interval, in milliseconds, at which the * SDK flushes logs to the remote server. The minimum allowed value is * 1000ms. * * When set to `null`, the SDK will never automatically flush logs, so you * you will need to flush logs manually using the `flushLogs` method. * * When `mode` is set to "off" the SDK will never automatically flush logs. * * @default 2000 */ flushIntervalMs?: number | null; }; /** * @deprecated use `logsHandler` instead */ localLogger?: LocalLogger; /** * By default, all message logs with level `Info` or higher are logged to the * console. * * You can set a `logsHandler` to handle logs yourself and override this * behavior, e.g. by logging `Debug` logs to the console too or forwarding * logs, including flag evaluations, analytics events and split exposures to * other analytics or observability systems. */ logsHandler?: LogsHandler; /** * @deprecated Use `shouldRefreshInitData` instead. */ shouldCheckForUpdates?: boolean; /** * @deprecated Use `lastInitDataRefreshTime` instead. */ lastDataProviderInitTime?: number | null; /** * @deprecated Use `initDataRefreshIntervalMs` and `shouldCheckForUpdates` * instead. */ initIntervalMs?: number; }; export type RemoteLoggingMode = "normal" | "off" | "session"; export type LocalLogger = ( level: LogLevel, message: string, metadata: object ) => void; export type DehydratedState< TOverride extends object, TVariableValues extends ObjectValue, > = { initData: InitData; lastInitDataRefreshTime: number | null; override: DeepPartial<TOverride> | null; variableValues: TVariableValues; }; export type DeepPartial<T> = | (T extends (infer U)[] ? DeepPartial<U>[] : T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T) | undefined; // Logs endpoint schema export type CreateLogsInput = { evaluations: EvaluationCountInput[]; events: EventInput[]; exposures: ExposureInput[]; idempotencyKey: string; logs: LogInput[]; token: string; }; export type LogInput = { commitId?: string | null; /** A JSON formatted Date string */ createdAt: string; level: LogLevel; message: string; /** JSON object containing metadata relating to the log */ metadataJson: string; type: LogType; }; // eslint-disable-next-line no-shadow export enum LogLevel { Debug = "Debug", Error = "Error", Info = "Info", Warn = "Warn", } // eslint-disable-next-line no-shadow export enum LogType { /** Codegen requests handled by Hypertune Edge */ Codegen = "Codegen", /** GraphQL requests handled by Hypertune Edge */ GraphQl = "GraphQL", /** Init requests handled by Hypertune Edge */ Init = "Init", /** JS requests handled by Hypertune Edge */ Js = "JS", /** SDK logs that aren't related to a specific Node */ SdkMessage = "SDKMessage", /** SDK logs related to a specific Node */ SdkNode = "SDKNode", /** Schema requests handled by Hypertune Edge */ // eslint-disable-next-line no-shadow Schema = "Schema", } export type EvaluationCountInput = { commitId: string; count: number; expressionId: string; }; export type EventInput = { commitId: string; createdAt: string; eventObjectTypeName?: string | null; eventPayloadJson?: string | null; eventTypeId?: string | null; unitId?: string | null; }; export type ExposureInput = { assignment: AssignmentInput[]; commitId: string; createdAt: string; eventObjectTypeName?: string | null; eventPayloadJson?: string | null; splitId: string; unitId: string; }; export type AssignmentInput = { continuousValue?: number | null; dimensionId: string; discreteArmId?: string | null; entryType: DimensionType; }; // eslint-disable-next-line no-shadow export enum DimensionType { Continuous = "Continuous", Discrete = "Discrete", }