UNPKG

@practical-fp/union-types

Version:

A Typescript library for creating discriminating union types.

343 lines 12 kB
/** * 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