@featurevisor/types
Version:
Common Typescript types for Featurevisor
622 lines (513 loc) • 13.4 kB
text/typescript
export type AttributeKey = string;
export interface AttributeObjectValue {
[key: AttributeKey]: AttributeValue;
}
export type AttributeValue =
| string
| number
| boolean
| Date
| null
| undefined
| string[]
| AttributeObjectValue;
export interface Context {
[key: AttributeKey]: AttributeValue;
}
export type AttributeType =
| "boolean"
| "string"
| "integer"
| "double"
| "date"
| "semver"
| "object"
| "array";
export interface Attribute {
archived?: boolean; // only available in YAML files
key?: AttributeKey; // needed for supporting v1 datafile generation
type: AttributeType;
description?: string; // only available in YAML files
properties?: {
[key: AttributeKey]: {
type:
| "boolean"
| "string"
| "integer"
| "double"
| "date"
| "semver"
// | "object" // NOTE: avoid nesting for now
| "array";
description?: string;
};
};
}
export type Operator =
| "equals"
| "notEquals"
| "exists"
| "notExists"
// numeric
| "greaterThan"
| "greaterThanOrEquals"
| "lessThan"
| "lessThanOrEquals"
// string
| "contains"
| "notContains"
| "startsWith"
| "endsWith"
// semver (string)
| "semverEquals"
| "semverNotEquals"
| "semverGreaterThan"
| "semverGreaterThanOrEquals"
| "semverLessThan"
| "semverLessThanOrEquals"
// date comparisons
| "before"
| "after"
// array of strings
| "includes"
| "notIncludes"
// regex
| "matches"
| "notMatches"
// array of strings
| "in"
| "notIn";
export type ConditionValue = string | number | boolean | Date | null | undefined | string[];
export interface PlainCondition {
attribute: AttributeKey;
operator: Operator;
value?: ConditionValue; // for all operators, except for "exists" and "notExists"
regexFlags?: string; // for regex operators only (matches, notMatches)
}
export interface AndCondition {
and: Condition[];
}
export interface OrCondition {
or: Condition[];
}
export interface NotCondition {
not: Condition[];
}
export type AndOrNotCondition = AndCondition | OrCondition | NotCondition;
export type Condition = PlainCondition | AndOrNotCondition | string;
export type SegmentKey = string;
export interface Segment {
archived?: boolean; // only available in YAML files
key?: SegmentKey; // needed for supporting v1 datafile generation
conditions: Condition | Condition[]; // string only when stringified for datafile
description?: string; // only available in YAML files
}
export type PlainGroupSegment = SegmentKey;
export interface AndGroupSegment {
and: GroupSegment[];
}
export interface OrGroupSegment {
or: GroupSegment[];
}
export interface NotGroupSegment {
not: GroupSegment[];
}
export type AndOrNotGroupSegment = AndGroupSegment | OrGroupSegment | NotGroupSegment;
// group of segment keys with and/or conditions, or just string
export type GroupSegment = PlainGroupSegment | AndOrNotGroupSegment;
export type VariationValue = string;
export type VariableKey = string;
export type VariableType =
| "boolean"
| "string"
| "integer"
| "double"
| "array"
| "object"
| "json";
export interface VariableObjectValue {
[key: string]: VariableValue;
}
export type VariableValue =
| boolean
| string
| number
| string[]
| VariableObjectValue
| null
| undefined;
export interface VariableOverrideSegments {
segments: GroupSegment | GroupSegment[];
}
export interface VariableOverrideConditions {
conditions: Condition | Condition[];
}
export type VariableOverrideSegmentsOrConditions =
| VariableOverrideSegments
| VariableOverrideConditions;
export interface VariableOverride {
value: VariableValue;
// one of the below must be present in YAML files
conditions?: Condition | Condition[];
segments?: GroupSegment | GroupSegment[];
}
export interface VariableV1 {
key: VariableKey;
value: VariableValue;
description?: string; // only available in YAML files
overrides?: VariableOverride[];
}
export interface VariationV1 {
description?: string; // only available in YAML files
value: VariationValue;
weight?: Weight; // 0 to 100 (available from parsed YAML, but not in datafile)
variables?: VariableV1[];
}
export interface Variation {
description?: string; // only available in YAML files
value: VariationValue;
weight?: Weight; // 0 to 100 (available from parsed YAML, but not in datafile)
variables?: {
[key: VariableKey]: VariableValue;
};
variableOverrides?: {
[key: VariableKey]: VariableOverride[];
};
}
export interface VariableSchema {
deprecated?: boolean;
key?: VariableKey; // @NOTE: remove
type: VariableType;
defaultValue: VariableValue;
description?: string; // only available in YAML files
useDefaultWhenDisabled?: boolean;
disabledValue?: VariableValue;
}
export type FeatureKey = string;
export interface Slot {
feature: FeatureKey | false;
percentage: Weight; // 0 to 100
}
export interface Group {
key: string;
description: string;
slots: Slot[];
}
export type BucketKey = string;
export type BucketValue = number; // 0 to 100,000 (100% * 1000 to include three decimal places in same integer)
/**
* Datafile-only types
*/
export type Percentage = number; // 0 to 100,000 (100% * 1000 to include three decimal places in same integer)
export type Range = [Percentage, Percentage]; // 0 to 100k
export interface Allocation {
variation: VariationValue;
range: Range;
}
export interface Traffic {
key: RuleKey;
segments: GroupSegment | GroupSegment[] | "*";
percentage: Percentage;
enabled?: boolean;
variation?: VariationValue;
variables?: {
[key: string]: VariableValue;
};
variationWeights?: {
[key: string]: Weight;
};
allocation?: Allocation[];
}
export type PlainBucketBy = AttributeKey;
export type AndBucketBy = AttributeKey[];
export interface OrBucketBy {
or: AttributeKey[];
}
export type BucketBy = PlainBucketBy | AndBucketBy | OrBucketBy;
export interface RequiredWithVariation {
key: FeatureKey;
variation: VariationValue;
}
export type Required = FeatureKey | RequiredWithVariation;
export interface Feature {
key?: FeatureKey; // needed for supporting v1 datafile generation
hash?: string;
deprecated?: boolean;
required?: Required[];
variablesSchema?: Record<VariableKey, VariableSchema>;
disabledVariationValue?: VariationValue;
variations?: Variation[];
bucketBy: BucketBy;
traffic: Traffic[];
force?: Force[];
ranges?: Range[]; // if in a Group (mutex), these are the available slot ranges
}
export interface FeatureV1 {
key?: FeatureKey;
hash?: string;
deprecated?: boolean;
required?: Required[];
bucketBy: BucketBy;
traffic: Traffic[];
force?: Force[];
ranges?: Range[]; // if in a Group (mutex), these are the available slot ranges
variablesSchema?: VariableSchema[];
variations?: VariationV1[];
}
export interface DatafileContentV1 {
schemaVersion: string;
revision: string;
attributes: Attribute[];
segments: Segment[];
features: FeatureV1[];
}
export interface DatafileContent {
schemaVersion: string;
revision: string;
segments: {
[key: SegmentKey]: Segment;
};
features: {
[key: FeatureKey]: Feature;
};
}
export interface EvaluatedFeature {
enabled: boolean;
variation?: VariationValue;
variables?: {
[key: VariableKey]: VariableValue;
};
}
export interface EvaluatedFeatures {
[key: FeatureKey]: EvaluatedFeature;
}
export type StickyFeatures = EvaluatedFeatures;
/**
* YAML-only type
*/
export type Weight = number; // 0 to 100
export type EnvironmentKey = string; // ideally "production", "staging", "testing", or "development" only
export type Tag = string;
export type RuleKey = string;
export interface Rule {
key: RuleKey;
description?: string; // only available in YAML
segments: GroupSegment | GroupSegment[];
percentage: Weight;
enabled?: boolean;
variation?: VariationValue;
variables?: {
[key: string]: VariableValue;
};
variationWeights?: {
[key: string]: Weight;
};
}
export interface RulesByEnvironment {
[key: EnvironmentKey]: Rule[];
}
export interface Force {
// one of the below must be present in YAML
conditions?: Condition | Condition[];
segments?: GroupSegment | GroupSegment[];
enabled?: boolean;
variation?: VariationValue;
variables?: {
[key: string]: VariableValue;
};
}
export interface ForceByEnvironment {
[key: EnvironmentKey]: Force[];
}
export type Expose = boolean | Tag[];
export interface ExposeByEnvironment {
[key: EnvironmentKey]: Expose;
}
export interface ParsedFeature {
key: FeatureKey;
archived?: boolean;
deprecated?: boolean;
description: string;
tags: Tag[];
required?: Required[];
bucketBy: BucketBy;
disabledVariationValue?: VariationValue;
variablesSchema?: Record<VariableKey, VariableSchema>;
variations?: Variation[];
expose?: ExposeByEnvironment | Expose;
force?: ForceByEnvironment | Force[];
rules?: RulesByEnvironment | Rule[];
}
/**
* For maintaining old allocations info,
* allowing for gradual rollout of new allocations
* with consistent bucketing
*/
export interface ExistingFeature {
hash?: string;
variations?: {
value: VariationValue;
weight: Weight;
}[];
traffic: {
key: RuleKey;
percentage: Percentage;
allocation?: Allocation[];
}[];
ranges?: Range[]; // if in a Group (mutex), these are the available slot ranges
}
export interface ExistingFeatures {
[key: FeatureKey]: ExistingFeature;
}
export interface ExistingState {
features: ExistingFeatures;
}
/**
* Tests
*/
export interface AssertionMatrix {
[key: string]: AttributeValue[];
}
export interface ExpectedEvaluations {
flag?: Record<string, any>;
variation?: Record<string, any>;
variables?: {
[key: VariableKey]: Record<string, any>;
};
}
export interface FeatureChildAssertion {
sticky?: StickyFeatures;
context?: Context;
defaultVariationValue?: VariationValue;
defaultVariableValues?: {
[key: string]: VariableValue;
};
expectedToBeEnabled?: boolean;
expectedVariation?: VariationValue;
expectedVariables?: {
[key: VariableKey]: VariableValue;
};
expectedEvaluations?: ExpectedEvaluations;
}
export interface FeatureAssertion {
matrix?: AssertionMatrix;
description?: string;
environment: EnvironmentKey;
at?: Weight; // bucket weight: 0 to 100
sticky?: StickyFeatures;
context?: Context;
defaultVariationValue?: VariationValue;
defaultVariableValues?: {
[key: string]: VariableValue;
};
expectedToBeEnabled?: boolean;
expectedVariation?: VariationValue;
expectedVariables?: {
[key: VariableKey]: VariableValue;
};
expectedEvaluations?: ExpectedEvaluations;
children?: FeatureChildAssertion[];
}
export interface TestFeature {
key?: string; // file path
feature: FeatureKey;
assertions: FeatureAssertion[];
}
export interface SegmentAssertion {
matrix?: AssertionMatrix;
description?: string;
context: Context;
expectedToMatch: boolean;
}
export interface TestSegment {
key?: string; // file path
segment: SegmentKey;
assertions: SegmentAssertion[];
}
export type Test = TestSegment | TestFeature;
export interface TestResultAssertionError {
type: "flag" | "variation" | "variable" | "segment" | "evaluation";
expected: string | number | boolean | Date | null | undefined;
actual: string | number | boolean | Date | null | undefined;
message?: string;
details?: {
evaluationType?: string; // e.g., "flag", "variation", "variable"
evaluationKey?: string; // e.g., "myFeatureKey", "myVariableKey"
childIndex?: number; // for children assertions
[key: string]: any;
};
}
export interface TestResultAssertion {
description: string;
duration: number;
passed: boolean;
errors?: TestResultAssertionError[];
}
export interface TestResult {
type: "feature" | "segment";
key: string;
notFound?: boolean;
passed: boolean;
duration: number;
assertions: TestResultAssertion[];
}
/**
* Site index and history
*/
export type EntityType = "attribute" | "segment" | "feature" | "group" | "test";
export type CommitHash = string;
export interface HistoryEntity {
type: EntityType;
key: string;
}
export interface HistoryEntry {
commit: CommitHash;
author: string;
timestamp: string;
entities: HistoryEntity[];
}
export interface LastModified {
commit: CommitHash;
timestamp: string;
author: string;
}
export interface SearchIndex {
links?: {
feature: string;
segment: string;
attribute: string;
commit: CommitHash;
};
projectConfig: {
tags: Tag[];
environments: EnvironmentKey[] | false;
};
entities: {
attributes: (Attribute & {
lastModified?: LastModified;
usedInSegments: SegmentKey[];
usedInFeatures: FeatureKey[];
})[];
segments: (Segment & {
lastModified?: LastModified;
usedInFeatures: FeatureKey[];
})[];
features: (ParsedFeature & {
lastModified?: LastModified;
})[];
};
}
export interface EntityDiff {
type: EntityType;
key: string;
created?: boolean;
deleted?: boolean;
updated?: boolean;
content?: string;
}
export interface Commit {
hash: CommitHash;
author: string;
timestamp: string;
entities: EntityDiff[];
}