@unsplash/sum-types-io-ts
Version:
io-ts bindings for @unsplash/sum-types.
320 lines • 12.7 kB
TypeScript
/**
* @since 0.1.0
*/
import * as Sum from "@unsplash/sum-types";
import * as t from "io-ts";
import * as O from "fp-ts/Option";
declare type Tag<A> = A extends Sum.Member<infer B, any> ? B : never;
declare type Value<A> = A extends Sum.Member<any, infer B> ? B : never;
declare type NullaryMember = Sum.Member<string>;
declare type Primitive = string | number | boolean | bigint | undefined | null;
/**
* Ensures that every key in union `A` is present at least once in array `B`.
*
* @link https://github.com/Microsoft/TypeScript/issues/13298#issuecomment-468733257
*/
declare type EveryKeyPresent<A, B> = Array<A> extends B ? B extends Array<A> ? B : never : never;
/**
* An object from member tags to codecs of their values.
*
* We require codecs for members without values as well as needing all of the
* keys to be present at runtime.
*
* `B` exists to support constraints on encoded representations.
*/
declare type MemberCodecs<A extends Sum.AnyMember, B = unknown> = {
readonly [C in A as Tag<C>]: t.Type<Value<C>, B>;
};
declare type OutputsOf<A extends Sum.AnyMember, B extends MemberCodecs<A>> = A extends Sum.AnyMember ? Sum.Member<Tag<A>, B[Tag<A>] extends t.Type<any, infer C> ? C : never> : never;
/**
* Derive a codec for nullary members to/from any other type. If the encoded
* representation forms part of an object then `nullaryFromEmpty` can be used
* instead. Incompatible with `Serialized` if not encoding to `null`.
*
* @example
* import * as t from "io-ts"
* import { nullaryFrom } from "@unsplash/sum-types-io-ts"
*
* // This will decode any object to null and encode to an empty object. Instead
* // consider `nullaryFromEmpty`.
* nullaryFrom({})(t.type({}))
*
* @since 0.7.0
*/
export declare const nullaryFrom: <A>(to: A) => (from: t.Type<A, unknown, unknown>) => t.Type<null, A, unknown>;
/**
* A representation of nullary member values that encodes to `undefined` for
* better JSON interop, and decodes from `undefined`, `null`, or empty objects
* (i.e. any object). Incompatible with `Serialized`.
*
* @since 0.7.0
*/
export declare const nullaryFromEmpty: t.Type<null, undefined | null | Record<string, unknown>>;
/**
* Derive a codec for `Serialized<A>` for any given sum `A` provided codecs for
* all its members` values.
*
* @since 0.1.0
*/
export declare const getSerializedCodec: <A extends Sum.AnyMember>() => <B extends MemberCodecs<A, unknown>>(cs: B, name?: string) => t.Type<Sum.Serialized<A>, Sum.Serialized<OutputsOf<A, B>>, unknown>;
/**
* Derive a codec for any given sum `A` provided codecs for all its members'
* values, decoding and encoding to/from `Serialized<A>`.
*
* @since 0.1.0
*/
export declare const getCodecFromSerialized: <A extends Sum.AnyMember>(sum: Sum.Sum<A>) => <B extends MemberCodecs<A, unknown>>(cs: B, name?: string) => t.Type<A, Sum.Serialized<OutputsOf<A, B>>, unknown>;
/**
* Derive a codec for any given sum `A` provided codecs for all its members'
* values.
*
* @since 0.1.0
*/
export declare const getCodec: <A extends Sum.AnyMember>(sum: Sum.Sum<A>) => <B extends MemberCodecs<A, unknown>>(cs: B, name?: string) => t.Type<A, OutputsOf<A, B>, unknown>;
/**
* Derive a codec for any given sum `A` in which all the constructors are
* nullary, decoding and encoding to/from the constructor tags via conversion
* functions. Consider instead `getCodecFromPrimitiveMappedNullaryTag` for
* stringly APIs.
*
* @example
* import * as t from "io-ts"
* import * as Sum from "@unsplash/sum-types"
* import { getCodecFromMappedNullaryTag } from "@unsplash/sum-types-io-ts"
* import * as O from "fp-ts/Option"
* import * as E from "fp-ts/Either"
*
* type Weather = Sum.Member<"Sun"> | Sum.Member<"Rain">
* const Weather = Sum.create<Weather>()
* type Country = "UK" | "Italy"
*
* const WeatherFromCountry: t.Type<Weather, Country> =
* getCodecFromMappedNullaryTag(Weather)(
* x => {
* switch (x) {
* case "Italy":
* return O.some("Sun")
* case "UK":
* return O.some("Rain")
* default:
* return O.none
* }
* },
* (x): Country => (x === "Sun" ? "Italy" : "UK"),
* )(["Sun", "Rain"])
*
* assert.deepStrictEqual(WeatherFromCountry.decode("UK"), E.right(Weather.mk.Rain))
*
* @since 0.3.0
*/
export declare const getCodecFromMappedNullaryTag: <A extends NullaryMember>(sum: Sum.Sum<A>) => <O, I>(from: (x: I) => O.Option<Tag<A>>, to: (x: Tag<A>) => O) => <C>(tags: EveryKeyPresent<Tag<A>, C>, name?: string) => t.Type<A, O, I>;
/**
* @since 0.5.1
*/
export declare class MappedType<A, B> extends t.Type<A, B, unknown> {
readonly Map: Record<Tag<A>, B>;
/**
* @since 0.5.1
*/
readonly _tag: "@unsplash/sum-types-io-ts/MappedType";
constructor(name: string, is: MappedType<A, B>["is"], validate: MappedType<A, B>["validate"], encode: MappedType<A, B>["encode"], Map: Record<Tag<A>, B>);
}
/**
* A convenient alternative to `getCodecFromMappedNullaryTag` for working with
* for example stringly APIs. The behaviour is unspecified if the input `Record`
* contains duplicate values.
*
* @example
* import * as t from "io-ts"
* import * as Sum from "@unsplash/sum-types"
* import { getCodecFromPrimitiveMappedNullaryTag } from "@unsplash/sum-types-io-ts"
* import * as E from "fp-ts/Either"
*
* type Weather = Sum.Member<"Sun"> | Sum.Member<"Rain">
* const Weather = Sum.create<Weather>()
* type Country = "UK" | "Italy"
*
* const WeatherFromCountry: t.Type<Weather, Country> =
* getCodecFromPrimitiveMappedNullaryTag(Weather)({ Sun: "Italy", Rain: "UK" })
*
* assert.deepStrictEqual(WeatherFromCountry.decode("UK"), E.right(Weather.mk.Rain))
*
* @since 0.5.0
*/
export declare const getCodecFromPrimitiveMappedNullaryTag: <A extends NullaryMember>(sum: Sum.Sum<A>) => <B extends Primitive>(tos: Record<Tag<A>, B>, name?: string) => MappedType<A, B>;
/**
* Derive a codec for any given sum `A` in which all the constructors are
* nullary, decoding and encoding to/from the constructor tags.
*
* @since 0.3.0
*/
export declare const getCodecFromNullaryTag: <A extends NullaryMember>(sum: Sum.Sum<A>) => <B>(tags: EveryKeyPresent<Tag<A>, B>, name?: string) => t.Type<A, string, unknown>;
/**
* Supports any encoded representation of nullary member values.
*
* @example
* ExternallyTagged<Weather, ...> = { Sun: null } | { Rain: { mm: number } }
*/
declare type ExternallyTagged<A extends Sum.AnyMember, B extends MemberCodecs<A>> = A extends Sum.AnyMember ? Record<Tag<A>, B[Tag<A>] extends t.Type<any, infer C> ? C : never> : never;
/**
* Derive a codec for any given sum `A` provided codecs for all its members'
* values, decoding and encoding to an object tagged by the sum member tag.
*
* Should the types overlap, the first valid codec will succeed.
*
* @example
* import * as t from "io-ts"
* import * as Sum from "@unsplash/sum-types"
* import { nullaryFromEmpty, getExternallyTaggedCodec } from "@unsplash/sum-types-io-ts"
* import * as E from "fp-ts/Either"
*
* type Weather = Sum.Member<"Sun"> | Sum.Member<"Rain", number>
* const Weather = Sum.create<Weather>()
*
* const WeatherCodec = getExternallyTaggedCodec(Weather)({
* Sun: nullaryFromEmpty,
* Rain: t.number,
* })
*
* assert.deepStrictEqual(
* WeatherCodec.decode({ Sun: undefined }),
* E.right(Weather.mk.Sun),
* )
*
* assert.deepStrictEqual(
* WeatherCodec.decode({ Rain: 123 }),
* E.right(Weather.mk.Rain(123)),
* )
*
* @since 0.7.0
*/
export declare const getExternallyTaggedCodec: <A extends Sum.AnyMember>(sum: Sum.Sum<A>) => <C extends MemberCodecs<A, unknown>>(cs: C, name?: string) => t.Type<A, ExternallyTagged<A, C>, unknown>;
/**
* Supports any encoded representation of nullary member values.
*
* @example
* AdjacentlyTagged<"k", "v", Weather, ...> = { k: Sun; v: null } | { k: Rain; v: { mm: number } }
*/
declare type AdjacentlyTagged<K extends string, V extends string, A extends Sum.AnyMember, B extends MemberCodecs<A>> = A extends Sum.AnyMember ? Record<K, Tag<A>> & Record<V, B[Tag<A>] extends t.Type<any, infer C> ? C : never> : never;
/**
* Derive a codec for any given sum `A` provided codecs for all its members'
* values, decoding and encoding to an object with sibling member tags and
* values.
*
* Due to the distinct, isolated member tag, it's not possible for overlaps to
* occur.
*
* @example
* import * as t from "io-ts"
* import * as Sum from "@unsplash/sum-types"
* import { nullaryFromEmpty, getAdjacentlyTaggedCodec } from "@unsplash/sum-types-io-ts"
* import * as E from "fp-ts/Either"
*
* type Weather = Sum.Member<"Sun"> | Sum.Member<"Rain", number>
* const Weather = Sum.create<Weather>()
*
* const WeatherCodec = getAdjacentlyTaggedCodec("tag")("value")(Weather)({
* Sun: nullaryFromEmpty,
* Rain: t.number,
* })
*
* assert.deepStrictEqual(
* WeatherCodec.decode({ tag: "Sun" }),
* E.right(Weather.mk.Sun),
* )
*
* assert.deepStrictEqual(
* WeatherCodec.decode({ tag: "Rain", value: 123 }),
* E.right(Weather.mk.Rain(123)),
* )
*
* @since 0.7.0
*/
export declare const getAdjacentlyTaggedCodec: <K extends string>(tagKey: K) => <V extends string>(valueKey: V) => <A extends Sum.AnyMember>(sum: Sum.Sum<A>) => <C extends MemberCodecs<A, unknown>>(cs: C, name?: string) => t.Type<A, AdjacentlyTagged<K, V, A, C>, unknown>;
/**
* Supports any encoded representation of nullary member values.
*
* @example
* Untagged<Weather, ...> = {} | { mm: number }
*/
declare type Untagged<A extends Sum.AnyMember, B extends MemberCodecs<A>> = A extends Sum.AnyMember ? B[Tag<A>] extends t.Type<any, infer C> ? C : never : never;
/**
* Derive a codec for any given sum `A` provided codecs for all its members'
* values, decoding and encoding directly to/from the member codecs.
*
* Should the types overlap, the first valid codec will succeed.
*
* @example
* import * as t from "io-ts"
* import * as Sum from "@unsplash/sum-types"
* import { nullaryFromEmpty, getUntaggedCodec } from "@unsplash/sum-types-io-ts"
* import * as E from "fp-ts/Either"
*
* type Weather = Sum.Member<"Sun"> | Sum.Member<"Rain", { mm: number }>
* const Weather = Sum.create<Weather>()
*
* const WeatherFromRainfall = getUntaggedCodec(Weather)({
* Rain: t.strict({ mm: t.number }),
* // This codec will match any object so it needs to come last.
* Sun: nullaryFromEmpty,
* })
*
* assert.deepStrictEqual(
* WeatherFromRainfall.decode({ mm: 123, foo: 'bar' }),
* E.right(Weather.mk.Rain({ mm: 123 })),
* )
*
* assert.deepStrictEqual(
* WeatherFromRainfall.decode({ foo: 'bar' }),
* E.right(Weather.mk.Sun),
* )
*
* @since 0.7.0
*/
export declare const getUntaggedCodec: <A extends Sum.AnyMember>(sum: Sum.Sum<A>) => <C extends MemberCodecs<A, unknown>>(cs: C, name?: string) => t.Type<A, Untagged<A, C>, unknown>;
/**
* Supports only empty objects as the encoded representation of nullary member
* values.
*
* @example
* InternallyTagged<"k", Weather, ...> = { k: Sun } | { k: Rain; mm: number }
*/
declare type InternallyTagged<K extends string, A extends Sum.AnyMember, B extends MemberCodecs<A>> = A extends Sum.AnyMember ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
Record<K, Tag<A>> & (B[Tag<A>] extends t.Type<any, infer C> ? C : never) : never;
/**
* Derive a codec for any given sum `A` provided codecs for all its members'
* values, decoding and encoding to an object with sibling member tags and
* values.
*
* Due to the distinct, isolated member tag, it's not possible for overlaps to
* occur.
*
* @example
* import * as t from "io-ts"
* import * as Sum from "@unsplash/sum-types"
* import { nullaryFromEmpty, getInternallyTaggedCodec } from "@unsplash/sum-types-io-ts"
* import * as E from "fp-ts/Either"
*
* type Weather = Sum.Member<"Sun"> | Sum.Member<"Rain", { mm: number }>
* const Weather = Sum.create<Weather>()
*
* const WeatherCodec = getInternallyTaggedCodec("tag")(Weather)({
* Sun: nullaryFromEmpty,
* Rain: t.strict({ mm: t.number }),
* })
*
* assert.deepStrictEqual(
* WeatherCodec.decode({ tag: "Sun" }),
* E.right(Weather.mk.Sun),
* )
*
* assert.deepStrictEqual(
* WeatherCodec.decode({ tag: "Rain", mm: 123 }),
* E.right(Weather.mk.Rain({ mm: 123 })),
* )
*
* @since 0.7.0
*/
export declare const getInternallyTaggedCodec: <K extends string>(tagKey: K) => <A extends Sum.AnyMember>(sum: Sum.Sum<A>) => <C extends MemberCodecs<A, Record<string, unknown> | null | undefined>>(cs: C, name?: string) => t.Type<A, InternallyTagged<K, A, C>, unknown>;
export {};
//# sourceMappingURL=index.d.ts.map