UNPKG

@unsplash/sum-types

Version:

Safe, ergonomic, non-generic sum types in TypeScript.

285 lines 8.68 kB
/** * 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