@unsplash/sum-types
Version:
Safe, ergonomic, non-generic sum types in TypeScript.
285 lines • 8.68 kB
TypeScript
/**
* The library's only entrypoint. Get started with `Member` and `create`.
*
* @example
* import { Member, create } from "@unsplash/sum-types"
*
* type Weather
* = Member<"Sun">
* | Member<"Rain", number>
*
* const { mk: { Sun, Rain }, match } = create<Weather>()
*
* const getRainfall = match({
* Rain: n => `${n}mm`,
* Sun: () => "none",
* })
*
* const todayWeather = Rain(5)
*
* getRainfall(todayWeather) // "5mm"
*
* @since 0.1.0
*/
/**
* Key used to index a sum type and access its tag. Not exposed with the
* intention that consumers make use of (de)serialization.
*/
declare const tagKey = "_tag";
declare type TagKey = typeof tagKey;
/**
* Key used to index a sum type and access its tag. Not exposed with the
* intention that consumers make use of (de)serialization.
*/
declare const valueKey = "_value";
declare type ValueKey = typeof valueKey;
/**
* The `Member` type represents a single member of a sum type given a name and
* an optional monomorphic value. A sum type is formed by unionizing these
* members.
*
* @example
* import { Member } from "@unsplash/sum-types"
*
* type Weather
* = Member<"Sun">
* | Member<"Rain", number>
*
* @since 0.1.0
*/
export interface Member<K extends string = never, A = null> {
readonly [tagKey]: K;
readonly [valueKey]: A;
}
/**
* @internal
*/
export declare type AnyMember = Member<string, unknown>;
declare type Tag<A extends AnyMember> = A[TagKey];
declare type Value<A extends AnyMember> = A[ValueKey];
declare type ValueByTag<A extends AnyMember, K extends Tag<A>> = Value<Extract<A, Member<K, unknown>>>;
/**
* Indexes a sum union by the member tag to determine the constructor shape.
* The third type argument can be inferred via its default.
*
* @internal
*/
export declare type Constructor<A extends AnyMember, K extends Tag<A>, V = ValueByTag<A, K>> = (x: V) => A;
/**
* Build a constructor for a member. If the member is nullary it won't be a
* function but a plain value.
*
* @internal
*/
export declare const mkConstructor: <A extends AnyMember = never>() => <T extends Tag<A>>(k: T) => Constructor<A, T, ValueByTag<A, T>>;
declare type Constructors<A extends AnyMember> = {
readonly [V in A as Tag<V>]: Constructor<A, Tag<V>>;
};
/**
* Symbol for declaring a wildcard case in a {@link match} expression.
*
* @example
* import { Member, create, _ } from "@unsplash/sum-types"
*
* type Weather
* = Member<"Sun">
* | Member<"Rain", number>
* | Member<"Clouds">
* | Member<"Overcast", string>
*
* const Weather = create<Weather>()
*
* const getSun = Weather.match({
* Sun: () => "sun",
* Overcast: () => "partial sun",
* [_]: () => "no sun",
* })
*
* assert.strictEqual(getSun(Weather.mk.Sun(null)), "sun")
* assert.strictEqual(getSun(Weather.mk.Clouds(null)), "no sun")
*
* @since 0.1.0
*/
export declare const _: unique symbol;
/**
* Ensures that a {@link match} expression covers all cases.
*/
declare type CasesExhaustive<A extends AnyMember, B> = {
readonly [V in A as Tag<V>]: (val: Value<V>) => B;
};
declare type CasesXExhaustive<A extends AnyMember, B> = {
readonly [V in A as Tag<V>]: B;
};
/**
* Enables a {@link match} expression to cover only some cases provided a
* wildcard case is declared with which to match the remaining cases.
*/
declare type CasesWildcard<A extends AnyMember, B> = {
readonly [K in keyof CasesExhaustive<A, B>]?: CasesExhaustive<A, B>[K];
} & {
readonly [_]: () => B;
};
declare type CasesXWildcard<A extends AnyMember, B> = {
readonly [K in keyof CasesXExhaustive<A, B>]?: CasesXExhaustive<A, B>[K];
} & {
readonly [_]: B;
};
/**
* Ensures that a {@link match} expression either covers all cases or contains
* a wildcard for matching the remaining cases.
*/
declare type Cases<A extends AnyMember, B> = CasesWildcard<A, B> | CasesExhaustive<A, B>;
declare type CasesX<A extends AnyMember, B> = CasesXWildcard<A, B> | CasesXExhaustive<A, B>;
declare type ReturnTypes<A extends Record<any, (...xs: ReadonlyArray<any>) => unknown>> = ReturnType<A[keyof A]>;
/**
* @internal
*/
export declare type MatchW<A extends AnyMember> = <B extends Cases<A, unknown>>(fs: B) => (x: A) => ReturnTypes<B>;
/**
* @internal
*/
export declare type MatchXW<A extends AnyMember> = <B extends CasesX<A, unknown>>(fs: B) => (x: A) => B[keyof B];
/**
* @internal
*/
export declare type Match<A extends AnyMember> = <B>(fs: Cases<A, B>) => (x: A) => B;
/**
* @internal
*/
export declare type MatchX<A extends AnyMember> = <B>(fs: CasesX<A, B>) => (x: A) => B;
/**
* The output of `create`, providing constructors and pattern matching.
*
* @internal
*/
export interface Sum<A extends AnyMember> {
/**
* An object of constructors for the sum type's members.
*
* @since 0.1.0
*/
readonly mk: Constructors<A>;
/**
* Pattern match against each member of a sum type. All members must
* exhaustively be covered unless a wildcard (@link \_) is present.
*
* @example
* match({
* Rain: (n) => `It's rained ${n} today!`,
* [_]: () => "Nice weather today.",
* })
*
* @since 0.1.0
*/
readonly match: Match<A>;
/**
* Pattern match against each member of a sum type. All members must
* exhaustively be covered unless a wildcard (@link \_) is present. Unionises
* the return types of the branches, hence the "W" suffix ("widen").
*
* @example
* matchW({
* Sun: () => 123,
* [_]: () => "the return types can be different",
* })
*
* @since 0.1.0
*/
readonly matchW: MatchW<A>;
/**
* Pattern match against each member of a sum type strictly, hence the "X"
* suffix ("strict"). All members must exhaustively be covered unless a
* wildcard (@link \_) is present.
*
* @example
* matchX({
* Sun: 123,
* [_]: 456,
* })
*
* @since 0.4.0
*/
readonly matchX: MatchX<A>;
/**
* Pattern match against each member of a sum type strictly, hence the "X"
* suffix ("strict"). All members must exhaustively be covered unless a
* wildcard (@link \_) is present. Unionises the return types of the branches,
* hence the "W" suffix ("widen").
*
* @example
* matchXW({
* Sun: 123,
* [_]: "the return types can be different",
* })
*
* @since 0.4.0
*/
readonly matchXW: MatchXW<A>;
}
/**
* Create runtime constructors and pattern matching functions for a given sum
* type.
*
* @example
* import { Member, create } from "@unsplash/sum-types"
*
* type Weather
* = Member<"Sun">
* | Member<"Rain", number>
*
* // Depending upon your preferences you may prefer to destructure the
* // returned object or effectively namespace it:
* const { mk: { Sun, Rain }, match } = create<Weather>()
* const Weather = create<Weather>()
*
* @since 0.1.0
*/
export declare const create: <A extends AnyMember>() => Sum<A>;
/**
* The serialized representation of a sum type, isomorphic to the sum type
* itself.
*
* @since 0.1.1
*/
export declare type Serialized<A> = A extends AnyMember ? readonly [Tag<A>, Value<A>] : never;
/**
* Serialize any sum type member into a tuple of its discriminant tag and its
* value (if any). It is recommended that you do this for any persistent data
* storage instead of relying upon how the library internally structures this
* data, which is an implementation detail. Reversible by `deserialize`.
*
* @since 0.1.0
*/
export declare const serialize: <A extends AnyMember>(x: A) => Serialized<A>;
/**
* Deserialize any prospective sum type member, represented by a tuple of its
* discriminant tag and its value (if any), into its programmatic data
* structure. Reversible by `serialize`.
*
* @since 0.1.0
*/
export declare const deserialize: <A extends AnyMember>(x: Sum<A>) => (y: Serialized<A>) => A;
/**
* Refine a foreign value to a sum type member given its key and a refinement to
* its value.
*
* This is a low-level primitive. Instead consider `@unsplash/sum-types-io-ts`.
*
* @example
* import { Member, create, is } from "@unsplash/sum-types"
*
* type Weather
* = Member<"Sun">
* | Member<"Rain", number>
* const Weather = create<Weather>()
*
* assert.strictEqual(
* is<Weather>()("Rain")((x): x is number => typeof x === 'number')(Weather.mk.Rain(123)),
* true,
* )
*
* @since 0.4.0
*/
export declare const is: <A extends AnyMember>() => <B extends Tag<A>>(k: B) => (f: (mv: unknown) => mv is ValueByTag<A, B>) => (x: unknown) => x is A;
export {};
//# sourceMappingURL=index.d.ts.map