UNPKG

@unsplash/sum-types-io-ts

Version:
320 lines 12.7 kB
/** * @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