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.
- Pre-requisite
- Utility types
- Cloned<T>
- Combinations<T>
- DeepExecWith<T>
- DeepNonNullable<T>
- DeepNonReadonly<T>
- DeepNullable<T>
- DeepPartial<T>
- DeepReadonly<T>
- DeepRequired<T>
- ExcludeKey<T, Key>
- ExtractKey<T, Key>
- Flatten<T>
- Merge<T, U>
- Nullish + Nullable<T, U>
- ObjectEntries<T>
- ObjectFromEntries<T>
- OmitKey<T>
- Pop<T>
- Shift<T>
- ShiftUntil<T, Key>
- TupleRecord<T>
- UnionToTuple<T>
- Unshift<T, Elements>
- Values<T>
- License
- TypeScript >= 4.3.5
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>;
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"]
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;
}>;
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;
}
];
};
}>>;
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;
}
];
};
}>>;
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>;
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;
}
];
};
}>;
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;
}
];
};
}>;
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;
}
];
};
}>>;
An aliased of Exclude but with typed Key
s 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">;
An aliased of Extract but with typed Key
s in T
.
type ExtractKey<T, Key extends keyof T> = Extract<keyof T, Key>;
// "b"
type AExtractKey = ExtractKey<{
a: boolean;
b: number;
c: string;
}, "b">;
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"]]]>;
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;
}>
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>;
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]>>;
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]>;
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">;
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"
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"]
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">; // []
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>;
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"]
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"]
type Values<T> = T extends {
[_ in keyof T]: infer U
} ? U : never;
// boolean | number | string
type AValues = Values<{
a: boolean;
b: number;
c: string;
}>;
MIT License © Rong Sen Ng