Skip to content

Instantly share code, notes, and snippets.

@zgover
Last active February 14, 2023 04:03
Show Gist options
  • Save zgover/8f8630411dbfe1ae9781775348e03b5b to your computer and use it in GitHub Desktop.
Save zgover/8f8630411dbfe1ae9781775348e03b5b to your computer and use it in GitHub Desktop.
[TS Missing Utility Types] Missing utility types for TypeScript #typescript #types #reusable #utility #utility-types

TypeScript utility types missing

Reusable utility types missing from TypeScript core library. Enjoy!

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Zachary Gover
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/** Any type of object record with string index types */
export type AnyProps = Record<string, unknown>

/** Dictionary collection with string index types (optionally specify value by setting T) */
export type Dictionary<T = unknown> = Record<string, T>

/** Dictionary collection optionally specify values to T */
export type EmptyObj<K extends keyof any = PropertyKey> = Record<K, never>

/** Type safe object "{}" record (optionally specify index type) */
export type AnyObj<K extends PropertyKey = PropertyKey> = Record<K, unknown>

/** Record with only readonly properties */
export type ReadonlyRecord<K extends keyof any, T> = Readonly<Record<K, T>>

/** From T, make all top level keys mutable (removes readonly) */
export type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}
/** From T, make all keys mutable including nested objects/arrays (removes readonly) */
export type DeeplyMutable<T> = {
  -readonly [P in keyof T]: (T[P] extends ReadonlyArray<infer U> ? DeeplyMutable<U>[] : DeeplyMutable<T[P]>)
}

/** From T, require properties whose keys are in union K (make specific keys required) */
export type PartRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
/** From T, make properties partial whose keys are in union K (Make specific keys optional) */
export type PartPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

/** From T, extract keys whose types are required (excludes optional properties) (optionally narrow keys in union by specifying K) */
export type RequiredKeysOnly<T, K extends keyof T = keyof T> = {
  [P in K]-?: (Pick<T, P> extends AnyObj ? never : P)
}[K]
/** From T, extract keys whose types are optional (excludes required properties) (optionally narrow keys in union by specifying K) */
export type PartialKeysOnly<T, K extends keyof T = keyof T> = {
  [P in K]-?: (Pick<T, P> extends AnyObj ? P : never)
}[K]

/** From T, pick a set of properties whose keys are in the union with required keys only, (optionally narrow results by specifying K) */
export type PickRequiredOnly<T, K extends keyof T = keyof T> = Pick<T, {
  [P in K]-?: (Pick<T, P> extends AnyObj ? never : P)
}[K]>
/** From T, pick a set of properties whose keys are in the union with partial keys only, (optionally narrow results by specifying K) */
export type PickPartialOnly<T, K extends keyof T = keyof T> = Pick<T, {
  [P in K]-?: (Pick<T, P> extends AnyObj ? P : never)
}[K]>

/** From T, rename a property whose key is in the union K (old key) with that of U (new key)  */
export type RenameKey<T, K extends keyof T, U extends string> = (Omit<T, K> & { [P in U]: T[K] })

/** With L (left), spread properties with R (right) (e.g. [...L, ...R], {...L, ...R}) */
export type Spreaded<L, R> = (
  /* With L (left), omit keys not in union with keys of R (right)*/
  Omit<L, keyof R>
  /* With R (right), omit keys in union with partial types*/
  & Omit<R, PartialKeysOnly<R>>
  /* With R (right), pick properties in union with partial types that do not exist in L (left)*/
  & Pick<R, Exclude<PartialKeysOnly<R>, keyof L>>
  /* With L (left), replace properties of the keys in union with the keys in R (right) excluding properties of R (right) with types in union with undefined */
  & { [P in (PartialKeysOnly<R> & (keyof L extends (keyof L & keyof R) ? (PartialKeysOnly<R> & keyof L) : never))]: (L[P] | Exclude<R[P], undefined>) }
  )
/// Spreaded usage example
/// const spread = <L, R>(left: L, right: R): Spreaded<L, R> => ({...left, ...right})
/// spread({a: 1, b: 2, d: 5}, {a: 3, b: 2, c: 4, d: undefined})
/// {"a":3,"b":2,"c":4,"d":undefined}

/** Implements a toString method */
export interface StringLike {
  toString(): string
  [Symbol.toStringTag]?(): string
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment