Skip to content

Instantly share code, notes, and snippets.

@toky-nomena
Created May 3, 2024 12:51
Show Gist options
  • Save toky-nomena/0f6c0f3c706117a597a0fce2c59df1b0 to your computer and use it in GitHub Desktop.
Save toky-nomena/0f6c0f3c706117a597a0fce2c59df1b0 to your computer and use it in GitHub Desktop.
type Join<K, P> = K extends string | number ?
P extends string | number ?
`${K}${"" extends P ? "" : "."}${P}`
: never : never;
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]
type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: K extends string | number ?`${K}` | Join<K, Paths<T[K], Prev[D]>> : never }[keyof T] : ""
type Leaves<T, D extends number = 10> = [D] extends [never]
? never
: T extends object
? { [K in keyof T]-?: Join<K, Leaves<T[K], Prev[D]>> }[keyof T]
: "";
@toky-nomena
Copy link
Author

export type Paths<T> = T extends Array<infer U>
  ? `${Paths<U>}`
  : T extends object
  ? {
      [K in keyof T & (string | number)]: K extends string
        ? `${K}` | `${K}.${Paths<T[K]>}`
        : never;
    }[keyof T & (string | number)]
  : never;

@toky-nomena
Copy link
Author

/* eslint-disable @typescript-eslint/no-explicit-any */
type Primitive = null | undefined | string | number | boolean | symbol | bigint;

/**
 * Checks whether T1 can be exactly (mutually) assigned to T2
 * @typeParam T1 - type to check
 * @typeParam T2 - type to check against
 * ```
 * IsEqual<string, string> = true
 * IsEqual<'foo', 'foo'> = true
 * IsEqual<string, number> = false
 * IsEqual<string, number> = false
 * IsEqual<string, 'foo'> = false
 * IsEqual<'foo', string> = false
 * IsEqual<'foo' | 'bar', 'foo'> = boolean // 'foo' is assignable, but 'bar' is not (true | false) -> boolean
 * ```
 */
type IsEqual<T1, T2> = T1 extends T2
  ? (<G>() => G extends T1 ? 1 : 2) extends <G>() => G extends T2 ? 1 : 2
    ? true
    : false
  : false;

interface File extends Blob {
  readonly lastModified: number;
  readonly name: string;
}

interface FileList {
  readonly length: number;
  item(index: number): File | null;
  [index: number]: File;
}

type BrowserNativeObject = Date | FileList | File;

/**
 * Type to query whether an array type T is a tuple type.
 * @typeParam T - type which may be an array or tuple
 * @example
 * ```
 * IsTuple<[number]> = true
 * IsTuple<number[]> = false
 * ```
 */
type IsTuple<T extends ReadonlyArray<any>> = number extends T['length']
  ? false
  : true;

/**
 * Type which given a tuple type returns its own keys, i.e. only its indices.
 * @typeParam T - tuple type
 * @example
 * ```
 * TupleKeys<[number, string]> = '0' | '1'
 * ```
 */
type TupleKeys<T extends ReadonlyArray<any>> = Exclude<keyof T, keyof any[]>;

/**
 * Helper function to break apart T1 and check if any are equal to T2
 *
 * See {@link IsEqual}
 */
type AnyIsEqual<T1, T2> = T1 extends T2
  ? IsEqual<T1, T2> extends true
    ? true
    : never
  : never;

/**
 * Helper type for recursively constructing paths through a type.
 * This actually constructs the strings and recurses into nested
 * object types.
 *
 * See {@link Path}
 */
type PathImpl<K extends string | number, V, TraversedTypes> = V extends
  | Primitive
  | BrowserNativeObject
  ? `${K}`
  : // Check so that we don't recurse into the same type
  // by ensuring that the types are mutually assignable
  // mutually required to avoid false positives of subtypes
  true extends AnyIsEqual<TraversedTypes, V>
  ? `${K}`
  : `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}`;

/**
 * Type which can be used to index an array or tuple type.
 */
type ArrayKey = number;

/**
 * Helper type for recursively constructing paths through a type.
 * This obscures the internal type param TraversedTypes from exported contract.
 *
 * See {@link Path}
 */
type PathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V>
  ? IsTuple<T> extends true
    ? {
        [K in TupleKeys<T>]-?: PathImpl<K & string, T[K], TraversedTypes>;
      }[TupleKeys<T>]
    : PathImpl<ArrayKey, V, TraversedTypes>
  : {
      [K in keyof T]-?: PathImpl<K & string, T[K], TraversedTypes>;
    }[keyof T];

/**
 * Type which eagerly collects all paths through a type
 * @typeParam T - type which should be introspected
 * @example
 * ```
 * Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
 * ```
 *
 * ---
 *
 * This type is borrowed directly from react-hook-form
 *
 * @see {@link https://github.com/react-hook-form/react-hook-form/blob/011fad503cc8d4543892f8e847b9bd58c1d9400f/src/types/path/eager.ts#L51-L61}
 */
// We want to explode the union type and process each individually
// so assignable types don't leak onto the stack from the base.
export type Path<T> = T extends any ? PathInternal<T> : never;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment