@featurevisor/types
Version:
Common Typescript types for Featurevisor
511 lines (415 loc) • 11.1 kB
text/typescript
export type AttributeKey = string;
export type AttributeValue = string | number | boolean | Date | null | undefined;
export interface Context {
[key: AttributeKey]: AttributeValue;
}
export type AttributeType = "boolean" | "string" | "integer" | "double" | "date" | "semver";
export interface Attribute {
archived?: boolean; // only available in YAML files
key: AttributeKey;
type: AttributeType;
capture?: boolean;
description?: string; // only available in YAML files
}
export type Operator =
| "equals"
| "notEquals"
// 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
| "in"
| "notIn";
export type ConditionValue = string | number | boolean | Date | null | undefined | string[];
export interface PlainCondition {
attribute: AttributeKey;
operator: Operator;
value: ConditionValue;
}
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;
export type SegmentKey = string;
export interface Segment {
archived?: boolean; // only available in YAML files
key: SegmentKey;
conditions: Condition | Condition[] | string; // 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 interface VariableOverrideBase {
value: VariableValue;
}
export type VariableOverrideSegmentsOrConditions =
| VariableOverrideSegments
| VariableOverrideConditions;
// export type VariableOverride = VariableOverrideBase & VariableOverrideSegmentsOrConditions;
export interface VariableOverride {
value: VariableValue;
// one of the below must be present in YAML files
// @TODO: try with above commented out TypeScript later
conditions?: Condition | Condition[];
segments?: GroupSegment | GroupSegment[];
}
export interface Variable {
key: VariableKey;
value: VariableValue;
description?: string; // only available in YAML files
overrides?: VariableOverride[];
}
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?: Variable[];
}
export interface VariableSchema {
deprecated?: boolean;
key: VariableKey;
type: VariableType;
defaultValue: VariableValue;
description?: string; // only available in YAML files
}
export type FeatureKey = string;
export interface Force {
// one of the below must be present in YAML
// @TODO: make it better with TypeScript
conditions?: Condition | Condition[];
segments?: GroupSegment | GroupSegment[];
enabled?: boolean;
variation?: VariationValue;
variables?: {
[key: string]: VariableValue;
};
}
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; // @TODO: in future, turn it into `ranges`, so that Allocations with same variation do not repeat
}
export interface Traffic {
key: RuleKey;
segments: GroupSegment | GroupSegment[] | "*";
percentage: Percentage;
enabled?: boolean;
variation?: VariationValue;
variables?: {
[key: string]: VariableValue;
};
allocation: Allocation[]; // @TODO: in v2, make it optional
}
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;
deprecated?: boolean;
required?: Required[];
variablesSchema?: VariableSchema[] | Record<VariableKey, VariableSchema>;
variations?: Variation[];
bucketBy: BucketBy;
traffic: Traffic[];
force?: Force[];
ranges?: Range[]; // if in a Group (mutex), these are the available slot ranges
}
export interface DatafileContentV1 {
schemaVersion: string;
revision: string;
attributes: Attribute[];
segments: Segment[];
features: Feature[];
}
export interface DatafileContentV2 {
schemaVersion: string;
revision: string;
attributes: {
[key: AttributeKey]: Attribute;
};
segments: {
[key: SegmentKey]: Segment;
};
features: {
[key: FeatureKey]: Feature;
};
}
export type DatafileContent = DatafileContentV1 | DatafileContentV2;
export interface OverrideFeature {
enabled: boolean;
variation?: VariationValue;
variables?: {
[key: VariableKey]: VariableValue;
};
}
export interface StickyFeatures {
[key: FeatureKey]: OverrideFeature;
}
export type InitialFeatures = StickyFeatures;
/**
* YAML-only type
*/
export type Weight = number; // 0 to 100
export type EnvironmentKey = string; // ideally "production", "staging", "testing", or "development" only
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;
};
}
export type Tag = string;
export type Expose = boolean | Tag[];
export interface Environment {
expose?: Expose;
rules: Rule[];
force?: Force[];
}
export interface ParsedFeature {
key: FeatureKey;
archived?: boolean;
deprecated?: boolean;
description: string;
tags: Tag[];
required?: Required[];
bucketBy: BucketBy;
variablesSchema?: VariableSchema[];
variations?: Variation[];
// if using environments
environments?: {
[key: EnvironmentKey]: Environment;
};
// if not using environments
expose?: Expose;
rules?: Rule[];
force?: Force[];
}
/**
* For maintaining old allocations info,
* allowing for gradual rollout of new allocations
* with consistent bucketing
*/
export interface ExistingFeature {
variations?: {
// @TODO: use Exclude with Variation?
value: VariationValue;
weight: Weight;
}[];
traffic: {
// @TODO: use Exclude with Traffic?
key: RuleKey;
percentage: Percentage;
allocation: Allocation[]; // @TODO: in v2, make it optional
}[];
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 FeatureAssertion {
matrix?: AssertionMatrix;
description?: string;
environment: EnvironmentKey;
at: Weight; // bucket weight: 0 to 100
context: Context;
expectedToBeEnabled: boolean;
expectedVariation?: VariationValue;
expectedVariables?: {
[key: VariableKey]: VariableValue;
};
}
export interface TestFeature {
feature: FeatureKey;
assertions: FeatureAssertion[];
}
export interface SegmentAssertion {
matrix?: AssertionMatrix;
description?: string;
context: Context;
expectedToMatch: boolean;
}
export interface TestSegment {
segment: SegmentKey;
assertions: SegmentAssertion[];
}
export type Test = TestSegment | TestFeature;
export interface TestResultAssertionError {
type: "flag" | "variation" | "variable" | "segment";
expected: string | number | boolean | Date | null | undefined;
actual: string | number | boolean | Date | null | undefined;
message?: string;
details?: object;
}
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[];
}