Created
May 3, 2024 12:51
-
-
Save toky-nomena/0f6c0f3c706117a597a0fce2c59df1b0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | |
: ""; |
Author
toky-nomena
commented
May 3, 2024
/* 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