@practical-fp/union-types
Version:
A Typescript library for creating discriminating union types.
343 lines • 12 kB
TypeScript
/**
* A type which discriminates on {@link tag}
* when used in a union with other instances of this type.
*
* @example
* type Union =
* | Variant<"1">
* | Variant<"2", number>
*/
export interface Variant<Tag extends string = string, Value = undefined> {
readonly tag: Tag;
readonly value: Value;
}
/**
* Utility type which allows any {@link Variant} to be assigned to it.
*/
export declare type AnyVariant = Variant<string, unknown>;
/**
* Creates a new {@link Variant} instance whose value is undefined.
* @param tag
*/
export declare function tag<Tag extends string>(tag: Tag): Variant<Tag>;
/**
* Creates a new {@link Variant} instance.
* @param tag
* @param value
*/
export declare function tag<Tag extends string, Value>(tag: Tag, value: Value): Variant<Tag, Value>;
/**
* Extracts the value form a @link Variant} instance.
* @param variant
*/
export declare function untag<Value>(variant: Variant<string, Value>): Value;
/**
* Utility type for extracting the possible values for {@link Variant#tag}
* from a union of {@link Variant}s.
*
* @example
* type Union =
* | Variant<"1">
* | Variant<"2">
*
* // Equals: "1" | "2"
* type UnionTags = Tags<Union>
*/
export declare type Tags<Var extends AnyVariant> = Var["tag"];
/**
* Utility type for extracting the possible types for {@link Variant#value}
* from a union of {@link Variant}s.
*
* @example
* type Union =
* | Variant<"1", string>
* | Variant<"2", number>
*
* // Equals: string | number
* type UnionValues = Values<Union>
*/
export declare type Values<Var extends AnyVariant> = Var["value"];
/**
* Utility type for narrowing down a union of {@link Variant}s based on their tags.
*
* @example
* type Union =
* | Variant<"1", 1>
* | Variant<"2", 2>
* | Variant<"3", 3>
*
* // Equals: Variant<"1", 1> | Variant<"3", 3>
* type Narrowed = Narrow<Union, "1" | "3">
*/
export declare type Narrow<Var extends AnyVariant, Tag extends Tags<Var>> = Extract<Var, Variant<Tag, unknown>>;
/**
* Type guard for narrowing down the type of a {@link Variant}.
* @param variant
* @param tag
* @example
* type Union =
* | Variant<"1", number>
* | Variant<"2", string>
*
* function doSomething(union: Union) {
* // union.value has type number | string
*
* if (hasTag(union, "1")) {
* // union.value has type number now
* }
* }
*/
export declare function hasTag<Var extends AnyVariant, Tag extends Tags<Var>>(variant: Var, tag: Tag): variant is Narrow<Var, Tag>;
/**
* Type of a function which narrows down the type of a given {@link Variant}.
*/
export declare type Predicate<Tag extends string> = <Var extends AnyVariant>(variant: Var) => variant is Narrow<Var, Tag>;
/**
* Factory function for creating a type guard which narrows down the type of a {@link Variant}.
* @param tag
* @example
* type Union =
* | Variant<"1", number>
* | Variant<"2", string>
*
* function doSomething(list: Union[]) {
* // filtered has type Variant<"1", number>[]
* const filtered = list.filter(predicate("1"))
* }
*/
export declare function predicate<Tag extends string>(tag: Tag): Predicate<Tag>;
/**
* Symbol for declaring a wildcard case in a {@link match} expression.
*/
export declare const WILDCARD: unique symbol;
/**
* Utility type for ensuring that a {@link matchExhaustive} expression covers all cases.
*/
export declare type CasesExhaustive<Var extends AnyVariant, Ret = unknown> = {
[Tag in Tags<Var>]: (value: Values<Narrow<Var, Tag>>) => Ret;
};
/**
* Utility type for enabling a {@link matchWildcard} expression to cover only some cases,
* as long as, a wildcard case is declared for matching the remaining cases.
*/
export declare type CasesWildcard<Var extends AnyVariant, Ret = unknown> = Partial<CasesExhaustive<Var, Ret>> & {
[WILDCARD]: () => Ret;
};
/**
* Utility type for ensuring that a {@link match} expression either covers all cases,
* or contains a wildcard for matching the remaining cases.
*/
export declare type Cases<Var extends AnyVariant, Ret = unknown> = CasesExhaustive<Var, Ret> | CasesWildcard<Var, Ret>;
/**
* Utility type for inferring the return type of a {@link match} expression.
*/
export declare type CasesReturn<Var extends AnyVariant, C extends Cases<Var>> = C extends Cases<Var, infer Ret> ? Ret : never;
/**
* Function for matching on the tag of a {@link Variant}.
* All possible cases need to be covered, unless a wildcard case is present.
* @param variant
* @param cases
* @example
* type Union =
* | Variant<"Num", number>
* | Variant<"Str", string>
* | Variant<"Bool", boolean>
*
* function doSomething(union: Union) {
* return match(union, {
* Num: number => number * number,
* Str: string => `Hello, ${string}!`,
* Bool: boolean => !boolean,
* })
* }
*
* function doSomethingElse(union: Union) {
* return match(union, {
* Str: string => `Hello, ${string}!`,
* [WILDCARD]: () => "Hello there!",
* })
* }
* @deprecated Use {@link matchExhaustive} or {@link matchWildcard} instead.
*/
export declare function match<Var extends AnyVariant, C extends Cases<Var>>(variant: Var, cases: C): CasesReturn<Var, C>;
/**
* Helper type to restrict the possible keys of a type.
*
* This is useful for {@link matchExhaustive} and {@link matchWildcard} where the cases argument
* needs to be generic to infer the correct return type.
* However, due to the argument being generic it is allowed to pass extra properties.
* Passing extra arguments is probably a spelling mistake.
* Therefore, we restrict the properties by setting extra properties to never.
*
* Typescript 4.2 will show a nice hint asking whether you've misspelled the property name.
*/
export declare type ValidateProperties<T, AllowedProperties extends keyof T> = {
[_ in Exclude<keyof T, AllowedProperties>]: never;
};
/**
* Function for matching on the tag of a {@link Variant}.
* All possible cases need to be covered.
* @param variant
* @param cases
* @example
* type Union =
* | Variant<"Num", number>
* | Variant<"Str", string>
* | Variant<"Bool", boolean>
*
* function doSomething(union: Union) {
* return matchExhaustive(union, {
* Num: number => number * number,
* Str: string => `Hello, ${string}!`,
* Bool: boolean => !boolean,
* })
* }
*/
export declare function matchExhaustive<Var extends AnyVariant, Cases extends CasesExhaustive<Var>>(variant: Var, cases: Cases & ValidateProperties<Cases, keyof CasesExhaustive<Var>>): CasesReturn<Var, Cases>;
/**
* Function for matching on the tag of a {@link Variant}.
* Not all cases need to be covered, a wildcard case needs to be present.
* @param variant
* @param cases
* @example
* type Union =
* | Variant<"Num", number>
* | Variant<"Str", string>
* | Variant<"Bool", boolean>
*
* function doSomething(union: Union) {
* return matchWildcard(union, {
* Str: string => `Hello, ${string}!`,
* [WILDCARD]: () => "Hello there!",
* })
* }
*/
export declare function matchWildcard<Var extends AnyVariant, Cases extends CasesWildcard<Var>>(variant: Var, cases: Cases & ValidateProperties<Cases, keyof CasesWildcard<Var>>): CasesReturn<Var, Cases>;
/**
* Utility function for asserting that all cases have been covered.
* @param variant
* @example
* type Union =
* | Variant<"1", string>
* | Variant<"2", number>
*
* function doSomething(union: Union) {
* switch(union.tag) {
* case "1":
* alert(union.value)
* break
* case "2":
* alert(union.value.toFixed(0))
* break
* default:
* // compile error if we've forgotten a case
* assertNever(union)
* }
* }
*/
export declare function assertNever(variant: never): never;
/**
* Type which specifies the constructor for a variant type.
*/
export declare type Constructor<Tag extends string, Value> = <T extends Value>(value: Value extends undefined ? T | void : T) => Variant<Tag, T>;
/**
* Type which specifies the strict constructor for a variant type.
* It does not support generics.
*/
export declare type StrictConstructor<Tag extends string, Value> = (value: Value extends undefined ? Value | void : Value) => Variant<Tag, Value>;
/**
* Type which specifies the extra properties which are attached to a constructor.
*/
export interface ConstructorExtra<Tag extends string> {
tag: Tag;
is: Predicate<Tag>;
}
/**
* Type which specifies the constructor for a variant type with attached type guard.
*/
export declare type ConstructorWithExtra<Tag extends string, Value> = Constructor<Tag, Value> & ConstructorExtra<Tag>;
/**
* Type which specifies the strict constructor for a variant type with attached type guard.
* It does not support generics.
*/
export declare type StrictConstructorWithExtra<Tag extends string, Value> = StrictConstructor<Tag, Value> & ConstructorExtra<Tag>;
/**
* Function for creating a constructor for the given variant.
*
* In case the variant type uses unconstrained generics,
* pass unknown as its type arguments.
*
* In case the variant type uses constrained generics,
* pass the constraint type as its type arguments.
*
* Use {@link impl} instead if your environment has support for {@link Proxy}.
*
* @example
* type Result<T, E> =
* | Variant<"Ok", T>
* | Variant<"Err", E>
*
* const Ok = constructor<Result<unknown, unknown>, "Ok">("Ok")
* const Err = constructor<Result<unknown, unknown>, "Err">("Err")
*
* let result: Result<number, string>
* result = Ok(42)
* result = Err("Something went wrong")
*
* Ok.is(result) // false
* Err.is(result) // true
*
* Ok.tag // "Ok"
* Err.tag // "Err"
*/
export declare function constructor<Var extends AnyVariant, Tag extends Tags<Var>>(tagName: Tag): ConstructorWithExtra<Tag, Values<Narrow<Var, Tag>>>;
/**
* Same as {@link constructor}, but does not support generics.
* @param tagName
*/
export declare function strictConstructor<Var extends AnyVariant, Tag extends Tags<Var>>(tagName: Tag): StrictConstructorWithExtra<Tag, Values<Narrow<Var, Tag>>>;
/**
* Type which specifies constructors and type guards for a variant type.
*/
export declare type Impl<Var extends AnyVariant> = {
[Tag in Tags<Var>]: ConstructorWithExtra<Tag, Values<Narrow<Var, Tag>>>;
};
/**
* Type which specifies strict constructors and type guards for a variant type.
* It does not support generics.
*/
export declare type StrictImpl<Var extends AnyVariant> = {
[Tag in Tags<Var>]: StrictConstructorWithExtra<Tag, Values<Narrow<Var, Tag>>>;
};
/**
* Function for generating an implementation for the given variants.
*
* In case the variant type uses unconstrained generics,
* pass unknown as its type arguments.
*
* In case the variant type uses constrained generics,
* pass the constraint type as its type arguments.
*
* @example
* type Result<T, E> =
* | Variant<"Ok", T>
* | Variant<"Err", E>
*
* const {Ok, Err} = impl<Result<unknown, unknown>>()
*
* let result: Result<number, string>
* result = Ok(42)
* result = Err("Something went wrong")
*
* Ok.is(result) // false
* Err.is(result) // true
*
* Ok.tag // "Ok"
* Err.tag // "Err"
*/
export declare function impl<Var extends AnyVariant>(): Impl<Var>;
/**
* Same as {@link impl}, but does not support generics.
*/
export declare function strictImpl<Var extends AnyVariant>(): StrictImpl<Var>;
//# sourceMappingURL=index.d.ts.map