# TypeScript Goodies ### AnyObject: [playground](https://t.ly/AWkS) ```typescript /** * Use this if you want to type "any object". * * @example * declare type AnyObject = Record<string, any> * * type ExpectsObject<T extends {}> = T * type BetterExpectsObject<T extends AnyObject> = T * * type Test1 = ExpectsObject<2> // this should not compile * type Test2 = BetterExpectsObject<2> // this throws an error like expected */ declare type AnyObject = Record<string, any>; ``` ### AnyValue ```typescript /** * Use this if you want a type meaning "any value". */ declare type AnyValue = unknown; ``` ### BetterArray: [playground](http://ibit.ly/XWea) ```typescript /** * `BetterArray` is capable of typing and compiling the four * `.reduce` calls below, while the base `Array` definitions will * throw errors or return incorrect types. * * @example * declare const array: BetterArray<number> * * array.reduce<string>((prev, current) => `${prev} ${current}`) * array.reduce((prev: number|string, current) => `${prev} ${current}`) * * array.reduce<number[]>((prev, current) => [ * ...(Array.isArray(prev) ? prev : [prev]), * current, * ]); * * declare const flatten: BetterArray<number | number[]> * * flatten.reduce((prev: number | number[], curr) => [ * ...(Array.isArray(prev) ? prev : [prev]), * ...(Array.isArray(curr) ? curr : [curr]), * ]) */ interface BetterArray<T> { reduce<R>(callback: (prev: T | T extends (infer V)[] ? V : T, curr: T, currIndex: number, array: T[]) => R): R reduce(callback: (prev: T, curr: T, currIndex: number, array: T[]) => T, initial: T): T reduce<U>(callback: (prev: U, curr: T, currIndex: number, array: T[]) => U, initial: U): U } ``` ### Discriminate: [playground](https://t.ly/xv4j) ```typescript /** * Create a discriminated union between the types in T. Either uses existing * `_type` field or generates a `_type` field based off the index of the type * in `Types`. * * @example * declare const test: Discriminate<[ * { apple: number }, * { banana: string }, * { carrot: boolean }, * ]> * * if (test._type === '0') * console.log(test.apple) * else if (test._type === '1') * console.log(test.banana) * else // test._type === '2' * console.log(test.carrot) */ declare type Discriminate<T extends [...any[]], Tracker extends any[] = []> = T extends [infer A, ...infer L] ? A extends AnyObject ? | ( A extends { _type: unknown } ? A : { [k in keyof A | '_type']: k extends '_type' ? `${Tracker['length']}` : A[k] } ) | Discriminate<[...L], [A, ...Tracker]> : never : never ``` ### EmptyObject: [playground](http://t.ly/6Cdp) ```typescript /** * Use this if you want a type meaning "empty object". * * @example * declare type EmptyObject = Record<string, never>; * * type ExpectEmpty<T extends {}> = any * type BetterExpectEmpty<T extends EmptyObject> = any * * type Test1 = ExpectEmpty<{ foo: boolean }>; // this should throw, but won't * type Test2 = BetterExpectEmpty<{ foo: boolean }>; // this correctly throws */ declare type EmptyObject = Record<string, never>; ``` ### MakeAllRequired: [playground](http://t.ly/SP60) ```typescript /** * Make all properties in T required * @example * type RequiredFoo = MakeAllRequired<{ foo?: string | null; bar?: number; }>; * * declare const test: RequiredFoo * test.foo // string * test.bar // number */ declare type MakeAllRequired<T extends AnyObject> = MakeRequired<T, keyof T>; ``` ### MakeOptional: [playground](http://t.ly/5x37) ```typescript /** * Make all properties which are assignable to K in T optional. * * @example * type OptionalFoo = MakeOptional<{ foo: string; bar: boolean }, 'foo'>; * declare const test: OptionalFoo; // { foo?: string; bar: boolean } */ declare type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [key in K]+?: Exclude<T[key], null | undefined> }; ``` ### MakeRequired: [playground](http://t.ly/qese) ```typescript /** * Make all properties which are assignable to K in T required. * * @example * type RequiredFoo = MakeRequired<{foo?: string | null}, 'foo'> * * declare const test: RequiredFoo * test.foo // string */ declare type MakeRequired<T, K extends keyof T> = Omit<T, K> & { [key in K]-?: Exclude<T[key], null | undefined> }; ``` ### OmitStrict: [playground](http://t.ly/xIBM) ```typescript /** * Omit with strict bounds on the keys that can be passed, they must extend * keyof T to be valid. Note: this is not ALWAYS useful, especially when making * types that are generic. The "loose"-typed `Omit` still has a purpose. * * @example * declare type OmitStrict<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; * * type Loose = Omit<{ foo: string }, 'baz'> * type Strict = OmitStrict<{ foo: string }, 'baz'> // TS error */ declare type OmitStrict<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; ``` ### Replace: [playground](http://t.ly/UV2d) ```typescript /** * Replace property Target in T with Replacement * @example * type Example = Replace<{foo: string}, 'foo', number> * * declare const test: Example * test.foo // number */ declare type Replace<T, Target extends keyof T, Replacement> = Omit<T, Target> & { [key in Target]: Replacement }; ``` ### Unbox: [playground](https://t.ly/zLip) ```typescript /** * Unboxes values from promises, functions, arrays and arrays recursively. * * Will stop at `limit` recursions. If `0` is provided will recurse until there * is nothing left to unbox. * * @example * // Basic examples * Unbox<()=>number> // number * Unbox<string> // string * Unbox<boolean[]> // boolean * Unbox<Promise<boolean>> // boolean * Unbox<() => Promise<() => Array<Promise<boolean>>>> // boolean * * // Recursive examples * Unbox<() => () => () => () => number> // number * Unbox<() => () => () => () => number, 3> // () => number * Unbox<() => Promise<() => Array<Promise<boolean>>>> // boolean */ declare type Unbox<T, Level extends number = 0, Visited extends number[] = [1]> = T extends Promise<infer V> ? ContinueUnboxing<V, Level, Visited> : T extends (infer V)[] ? ContinueUnboxing<V, Level, Visited> : T extends (...args: unknown[]) => infer V ? ContinueUnboxing<V, Level, Visited> : T type ContinueUnboxing<T, L extends number, V extends number[]> = L[] extends never[] ? Unbox<T, L> : L extends V['length'] ? T : Unbox<T, L, [...V, 1]> ```