Skip to content

reallyland/ts-util-types

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 

Repository files navigation

ts-util-types

Commonly used utility types in TypeScript


Buy Me A Coffee tippin.me Follow me

MIT License

Opinionated utility types in TypeScript

This hosts all the snippets for different utility types that are useful for certain use cases but are not currently supported natively.

Table of contents

Pre-requisite

Utility types

Cloned<T>

type Cloned<T> = DeepExecWith<T, {
    [K in keyof T]: Cloned<T[K]>;
}>;
interface A {
  a: boolean;
  b: number;
  c: string;
}

// {
//   a: boolean;
//   b: number;
//   c: string;
// }
type ACloned = Cloned<A>;

// A & { d: boolean }
type B = A & { d: boolean };

// {
//   a: boolean;
//   b: number;
//   c: string;
//   d: boolean;
// }
type BCloned = Cloned<B>;

Combinations<T>

type IsEmptyArray_<T extends readonly unknown[]> = T extends readonly [] ? true : false;
type CombinationsUtil_<T extends readonly unknown[], Key = T[number]> =
  Key extends Key
    ? [Key] | (
      IsEmptyArray_<ShiftUntil<T, Key>> extends true
        ? never
        : [Key, ...CombinationsUtil_<ShiftUntil<T, Key>>]
    )
    : never;

type Combinations<T extends readonly unknown[]> =
  T extends readonly unknown[]
    ? T extends unknown[]
      ? CombinationsUtil_<T>
      : Readonly<CombinationsUtil_<T>>
    : never;
type ACombinations = Combinations<readonly []>; // never
type BCombinations = Combinations<[]>; // never

type CCombinations = Combinations<readonly ["a"]>; // readonly ["a"]
type DCombinations = Combinations<["a"]>; // ["a"]

type ECombinations = Combinations<readonly ["a", "b"]>; // readonly ["a"] | readonly ["b"] | readonly ["a", "b"]
type FCombinations = Combinations<["a", "b"]>; // ["a"] | ["b"] | ["a", "b"]

type GCombinations = Combinations<readonly ["a", "b", "c"]>; // readonly ["a"] | readonly ["b"] | readonly ["c"] | readonly ["a", "b"] | readonly ["a", "c"] | readonly ["b", "c"] | readonly ["a", "b", "c"]
type HCombinations = Combinations<["a", "b", "c"]>; // ["a"] | ["b"] | ["c"] | ["a", "b"] | ["a", "c"] | ["b", "c"] | ["a", "b", "c"]

DeepExecWith<T>

type DeepExecWith<T, Condition> = T extends Record<infer _Key, unknown>
    ? Condition
    : T extends (...args: any) => unknown
        ? T
        : Condition;
type NeverPrimitives<T> = DeepExecWith<T, {
    [K in keyof T]: T[K] extends object ? NeverPrimitives<T[K]> : never;
}>;

// {
//     a: () => void;
//     b: {
//         c: never;
//         d: [never, never, never];
//         e: (a?: string | undefined) => Promise<void>;
//         f: never;
//         g: never;
//         h: never;
//         i: {
//             toString: () => string;
//             valueOf: () => symbol;
//             readonly description: never;
//         };
//         j: {
//             toString: (radix?: number | undefined) => string;
//             toLocaleString: (locales?: string | undefined, options?: BigIntToLocaleStringOptions | undefined) => string;
//             valueOf: () => bigint;
//         };
//         k: never;
//     };
//     c: never;
//     d: [never, never, never];
//     e: (a?: string | undefined) => Promise<void>;
//     f: never;
//     g: never;
//     h: never;
//     i: {
//         toString: () => string;
//         valueOf: () => symbol;
//         readonly description: never;
//     };
//     j: {
//         toString: (radix?: number | undefined) => string;
//         toLocaleString: (locales?: string | undefined, options?: BigIntToLocaleStringOptions | undefined) => string;
//         valueOf: () => bigint;
//     };
//     k: never;
// }
type ANeverPrimitives = NeverPrimitives<{
    a(): void;
    b: {
        c: 1;
        d: [1, 2, 3];
        e(a?: string): Promise<void>;
        f: "1";
        g: null;
        h: undefined;
        i: Symbol;
        j: BigInt;
        k: true;
    };
    c: 1;
    d: [1, 2, 3];
    e(a?: string): Promise<void>;
    f: "1";
    g: null;
    h: undefined;
    i: Symbol;
    j: BigInt;
    k: true;
}>;

DeepNonNullable<T>

type DeepNonNullable<T> = Cloned<DeepExecWith<T, NonNullable<{
    [K in keyof T]: DeepNonNullable<T[K]>;
}>>>;
// {
//     a: boolean;
//     b: [{
//         c: boolean;
//     }];
//     d: {
//         e: boolean;
//         f: [{
//             g: boolean;
//         }];
//     };
// }
type ADeepNonNullable = DeepNonNullable<DeepNullable<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}>>;

DeepNonReadonly<T>

type DeepNonReadonly<T> = Cloned<DeepExecWith<T, {
    -readonly [K in keyof T]: DeepNonReadonly<T[K]>;
}>>;
// {
//     a: boolean;
//     b: [{
//         c: boolean;
//     }];
//     d: {
//         e: boolean;
//         f: [{
//             g: boolean;
//         }];
//     };
// }
type ADeepNonReadonly = DeepNonReadonly<DeepReadonly<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}>>;

DeepNullable<T>

type DeepNullable<T, U extends Nullish = null | undefined> = Cloned<DeepExecWith<T, {
    [K in keyof T]: DeepNullable<T[K], U> | U;
}>>;
// {
//     a: boolean | null | undefined;
//     b: [{
//         c: boolean | null | undefined;
//     } | null | undefined] | null | undefined;
//     d: {
//         e: boolean | null | undefined;
//         f: [{
//             g: boolean | null | undefined;
//         } | null | undefined] | null | undefined;
//     } | null | undefined;
// }
type ANullable = DeepNullable<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}>;

// {
//     a: boolean | null;
//     b: [{
//         c: boolean | null;
//     } | null] | null;
//     d: {
//         e: boolean | null;
//         f: [{
//             g: boolean | null;
//         } | null] | null;
//     } | null;
// }
type ANullableNull = DeepNullable<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}, null>;

// {
//     a: boolean | undefined;
//     b: [{
//         c: boolean | undefined;
//     } | undefined] | undefined;
//     d: {
//         e: boolean | undefined;
//         f: [{
//             g: boolean | undefined;
//         } | undefined] | undefined;
//     } | undefined;
// }
type ANullableUndefined = DeepNullable<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}, undefined>;

DeepPartial<T>

type DeepPartial<T> = Cloned<DeepExecWith<T, {
    [K in keyof T]?: DeepPartial<T[K]>;
}>>;
// {
//     a?: boolean | undefined;
//     b?: [({
//         c?: boolean | undefined;
//     } | undefined)?] | undefined;
//     d?: {
//         e?: boolean | undefined;
//         f?: [({
//             g?: boolean | undefined;
//         } | undefined)?] | undefined;
//     } | undefined;
// }
type ADeepPartial = DeepPartial<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}>;

DeepReadonly<T>

type DeepReadonly<T> = Cloned<DeepExecWith<T, {
    readonly [K in keyof T]: DeepReadonly<T[K]>;
}>>;
// {
//     readonly a: boolean;
//     readonly b: readonly [{
//         readonly c: boolean;
//     }];
//     readonly d: {
//         readonly e: boolean;
//         readonly f: readonly [{
//             readonly g: boolean;
//         }];
//     };
// }
type ADeepReadonly = DeepReadonly<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}>;

DeepRequired<T>

type DeepRequired<T> = Cloned<DeepExecWith<T, {
    [K in keyof T]-?: DeepRequired<T[K]>;
}>>;
// {
//     a: boolean;
//     b: [{
//         c: boolean;
//     }];
//     d: {
//         e: boolean;
//         f: [{
//             g: boolean;
//         }];
//     };
// }
type ADeepRequired = DeepRequired<DeepPartial<{
    a: boolean;
    b: [
        {
            c: boolean;
        }
    ];
    d: {
        e: boolean;
        f: [
            {
                 g: boolean;
            }
        ];
    };
}>>;

ExcludeKey<T, Key>

An aliased of Exclude but with typed Keys in T.

type ExcludeKey<T, Key extends keyof T> = Exclude<keyof T, Key>;
// "a" | "c"
type AExcludeKey = ExcludeKey<{
  a: boolean;
  b: number;
  c: string;
}, "b">;

ExtractKey<T, Key>

An aliased of Extract but with typed Keys in T.

type ExtractKey<T, Key extends keyof T> = Extract<keyof T, Key>;
// "b"
type AExtractKey = ExtractKey<{
  a: boolean;
  b: number;
  c: string;
}, "b">;

Flatten<T>

type Flatten<T extends readonly unknown[]> =
  T extends readonly [infer ReadonlyFirst, ...infer ReadonlyRest]
    ? T extends [infer First, ...infer Rest]
      ? [
          ...(
            First extends readonly unknown[]
                ? Flatten<First>
                : [First]
          ),
          ...Flatten<Rest>
        ]
      : Readonly<[
          ...(
            ReadonlyFirst extends readonly unknown[]
                ? Flatten<ReadonlyFirst>
                : Readonly<[ReadonlyFirst]>
          ),
          ...Flatten<ReadonlyRest>
        ]>
    : T;
// ["a", "b", "c"]
type AFlatten = Flatten<[["a"], "b", [["c"]]]>;
type BFlatten = Flatten<[["a"], "b", [readonly ["c"]]]>;

// readonly ["a", "b", "c"]
type CFlatten = Flatten<readonly [["a"], "b", [["c"]]]>;
type DFlatten = Flatten<readonly [["a"], "b", readonly [readonly ["c"]]]>;
type EFlatten = Flatten<readonly [readonly ["a"], "b", [["c"]]]>;
type FFlatten = Flatten<readonly [readonly ["a"], "b", [readonly ["c"]]]>;
type GFlatten = Flatten<readonly [readonly ["a"], "b", readonly [readonly ["c"]]]>;
type HFlatten = Flatten<[readonly ["a"], "b", [readonly ["c"]]]>;

Merge<T, U>

type Merge<T, U> = Cloned<Omit<T, keyof U> & U>;
interface A {
  a: boolean;
  b: number;
  c: string;
}

interface B {
  c: boolean;
  d: number;
}

// {
//   a: boolean;
//   b: number;
//   c: boolean;
//   d: number;
// }
type ABMerge = Merge<A, B>;

// {
//   a: boolean;
//   b: number;
//   c: boolean;
//   d: number;
// }
type AMerge = Merge<A, {
  c: boolean;
  d: number;
}>

Nullish + Nullable<T, U>

type Nullish = null | undefined;

// Workaround to expand Nullish with U extends Nullish
type Nullable<T, U extends Nullish = Nullish> = Cloned<U extends Nullish ? T | U : T | U>;
// number | Nullish
type ANullish = number | Nullish;

// number | null | undefined
type ANullish2 = Cloned<number | Nullish>;

// number | null | undefined;
type ANullable = Nullable<number>;

// number | null;
type ANullableNull = Nullable<number, null>;

// number | undefined;
type ANullableUndefined = Nullable<number, undefined>;

ObjectEntries<T>

type ObjectEntries<T> =
  T extends Record<infer Key, unknown>
      ? T extends readonly unknown[]
        ? never
        : Key extends Key
          ? [Key, T[Key]]
          : never
    : never;
// ["a", 1] | ["b", "2"] | ["c", () => true]
type AObjectEntries = ObjectEntries<{ a: 1, b: "2", c: () => true }>;
type BObjectEntries = ObjectEntries<Readonly<{ a: 1, b: "2", c: () => true }>>;

// never
type CObjectEntries = ObjectEntries<() => true>;
type DObjectEntries = ObjectEntries<Readonly<() => true>>;
type EObjectEntries = ObjectEntries<[1, "2", true, () => true]>;
type FObjectEntries = ObjectEntries<Readonly<[1, "2", true, () => true]>>;

ObjectFromEntries<T>

type ObjectFromEntries<T extends readonly [number | string | symbol, unknown]> = {
  [K in T[0]]: T extends readonly [K, infer Value] ? Value : never;
};
// {
//     a: 1;
//     b: "2";
//     c: () => true;
// }
type AObjectFromEntries = ObjectFromEntries<["a", 1] | ["b", "2"] | ["c", () => true]>;

// {
//     a: 1;
//     b: "2";
//     c: () => true;
// }
type BObjectFromEntries = ObjectFromEntries<[1, "a"] | [2, true]>;

// {
//     1: "a";
// }
type CObjectFromEntries = ObjectFromEntries<readonly [1, "a"]>;

// {
//     a: 1;
// }
type DObjectFromEntries = ObjectFromEntries<readonly ["a", 1]>;

OmitKey<T>

type OmitKey<T, K extends keyof T> = Omit<T, K>;
interface A {
    a: boolean;
    b: number;
    c: string;
}

// {
//     a: boolean;
//     c: string;
// }
type AOmitKey = OmitKey<A, "b">;

// {
//     c: string;
// }
type BOmitKey = OmitKey<A, "b" | "a">;

Pop<T>

type Pop<T extends readonly unknown[]> =
  T extends readonly [...infer _ReadonlyRest, infer ReadonlyLast]
    ? T extends [...infer _Rest, infer Last]
      ? Last
      : Readonly<ReadonlyLast>
    : never;
type APop = Pop<["a"]>; // "a"
type BPop = Pop<["a", "b"]>; // "b"
type CPop = Pop<readonly ["a"]>; // "a"
type DPop = Pop<readonly ["a", "b"]>; // "b"

Shift<T>

type Shift<T extends readonly unknown[]> =
    T extends readonly [infer _ReadonlyFirst, ...infer ReadonlyRest]
        ? T extends [infer _First, ...infer Rest]
            ? Rest
            : Readonly<ReadonlyRest>
        : [];
type AShift = Shift<readonly ["a"]>; // readonly []
type BShift = Shift<readonly ["a", "b"]>; // readonly ["b"]
type CShift = Shift<readonly ["a", "b", "c"]>; // readonly ["b", "c"]

type AShift2 = Shift<["a"]>; // []
type BShift2 = Shift<["a", "b"]>; // ["b"]
type CShift2 = Shift<["a", "b", "c"]>; // ["b", "c"]

ShiftUntil<T, Key>

type ShiftUntil<T extends readonly unknown[], Key extends T[number]> =
  T extends readonly [infer ReadonlyFirst, ...infer ReadonlyRest]
    ? T extends [infer First, ...infer Rest]
      ? First extends Key
        ? Rest
        : ShiftUntil<Rest, Key>
      : ReadonlyFirst extends Key
        ? Readonly<ReadonlyRest>
        : ShiftUntil<Readonly<ReadonlyRest>, Key>
    : T;
type AShiftUntil = ShiftUntil<readonly ["a"], "a">; // readonly []
type AShiftUntil2 = ShiftUntil<["a"], "a">; // []

type BShiftUntil = ShiftUntil<readonly ["a", "b"], "a">; // readonly ["b"]
type BShiftUntil2 = ShiftUntil<readonly ["a", "b"], "b">; // readonly []
type BShiftUntil3 = ShiftUntil<["a", "b"], "a">; // ["b"]
type BShiftUntil4 = ShiftUntil<["a", "b"], "b">; // []

type CShiftUntil = ShiftUntil<readonly ["a", "b", "c"], "a">; // readonly ["b", "c"]
type CShiftUntil2 = ShiftUntil<readonly ["a", "b", "c"], "b">; // readonly ["c"]
type CShiftUntil3 = ShiftUntil<readonly ["a", "b", "c"], "c">; // readonly []
type CShiftUntil4 = ShiftUntil<["a", "b", "c"], "a">; // ["b", "c"]
type CShiftUntil5 = ShiftUntil<["a", "b", "c"], "b">; // ["c"]
type CShiftUntil6 = ShiftUntil<["a", "b", "c"], "c">; // []

TupleRecord<T>

type TupleRecord<T extends (unknown [] | ReadonlyArray<unknown>)> = DeepReadonly<{
    keys: T[number];
    values: T;
}>;
// {
//     readonly keys: "a" | "b";
//     readonly values: readonly ["a", "b"];
// }
type ATupleRecord = TupleRecord<[
    "a",
    "b",
]>

const a2 = ["a", "b"] as const;

// {
//     readonly keys: "a" | "b";
//     readonly values: readonly ["a", "b"];
// }
type ATupleRecord2 = TupleRecord<typeof a2>;

UnionToTuple<T>

type UnionFn_<T> = T extends T ? (arg: T) => 0 : never;
type UnionFnToIntersectionFn_<T> = (T extends T ? (arg: T) => 0 : never) extends (arg: infer U) => 0 ? U : never;
type LastUnionFromIntersectionFn_<T> = T extends (arg: infer U) => 0 ? U : never;
type LastUnion_<T> = LastUnionFromIntersectionFn_<
  UnionFnToIntersectionFn_<
    UnionFn_<T>
  >
>;
type UnionToTupleUtil_<T, LastKey = LastUnion_<T>> =
  [T] extends [never]
    ? []
    : [...UnionToTupleUtil_<Exclude<T, LastKey>>, LastKey];

type UnionToTuple<T> = UnionToTupleUtil_<T>;
// ((arg: "a") => 0) | ((arg: "b") => 0)
type AUnionFn_ = UnionFn_<"a" | "b">;

// ((arg: "a") => 0) & ((arg: "b") => 0)
type AUnionFnToIntersectionFn_ = UnionFnToIntersectionFn_<AUnionFn_>;

// "b"
type ALastUnion_ = LastUnionFromIntersectionFn_<AUnionFnToIntersectionFn_>;

type AUnionToTuple = UnionToTuple<"a" | "b">; // ["a", "b"]
type AUnionToTuple2 = UnionToTuple<"a" | "b" | "c">; // ["a", "b", "c"]

Unshift<T, Elements>

type Unshift<T extends readonly unknown[], Elements extends readonly unknown[]> =
    T extends readonly unknown[]
        ? T extends unknown[]
          ? [...Elements, ...T]
          : Readonly<[...Elements, ...T]>
        : T;
type AUnshift = Unshift<["a"], ["b"]>; // ["b", "c"]
type BUnshift = Unshift<["a"], ["b", "c"]>; // ["b", "c", "a"]
type CUnshift = Unshift<readonly ["a"], ["b"]>; // readonly ["b", "a"]
type DUnshift = Unshift<readonly ["a"], ["b", "c"]>; // readonly ["b", "c", "a"]
type EUnshift = Unshift<["a"], readonly ["b"]>; // ["b", "a"]
type FUnshift = Unshift<["a"], readonly ["b", "c"]>; // ["b", "c", "a"]
type GUnshift = Unshift<readonly ["a"], readonly ["b"]>; // readonly ["b", "a"]
type HUnshift = Unshift<readonly ["a"], readonly ["b", "c"]>; // readonly ["b", "c", "a"]

Values<T>

type Values<T> = T extends {
    [_ in keyof T]: infer U
} ? U : never;
// boolean | number | string
type AValues = Values<{
  a: boolean;
  b: number;
  c: string;
}>;

License

MIT License © Rong Sen Ng

About

Utility types in TypeScript

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published