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
text/typescript
/* 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",
}