Created
August 9, 2023 02:43
-
-
Save imekachi/dad5ca6ae13abd28e3d1efd3faa3ee36 to your computer and use it in GitHub Desktop.
Type-safe lodash set
This file contains hidden or 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
import _set from 'lodash/set' | |
export const set = < | |
Obj extends UnknownObject, | |
KeyPath extends Path<Obj>, | |
Value extends PathValue<Obj, KeyPath> | |
>( | |
obj: Obj, | |
path: KeyPath, | |
value: Value | |
) => _set(obj, path, value) | |
/** | |
* Helper function to break apart T1 and check if any are equal to T2 | |
* | |
* See {@link IsEqual} | |
*/ | |
export 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}` | |
: true extends AnyIsEqual<TraversedTypes, V> | |
? `${K}` | |
: `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}` | |
/** | |
* 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' | |
* ``` | |
*/ | |
export type Path<T> = T extends any ? PathInternal<T> : never | |
/** | |
* Helper type for recursively constructing paths through a type. | |
* This actually constructs the strings and recurses into nested | |
* object types. | |
* | |
* See {@link ArrayPath} | |
*/ | |
type ArrayPathImpl<K extends string | number, V, TraversedTypes> = V extends | |
| Primitive | |
| BrowserNativeObject | |
? IsAny<V> extends true | |
? string | |
: never | |
: V extends ReadonlyArray<infer U> | |
? U extends Primitive | BrowserNativeObject | |
? IsAny<V> extends true | |
? string | |
: never | |
: true extends AnyIsEqual<TraversedTypes, V> | |
? never | |
: `${K}` | `${K}.${ArrayPathInternal<V, TraversedTypes | V>}` | |
: true extends AnyIsEqual<TraversedTypes, V> | |
? never | |
: `${K}.${ArrayPathInternal<V, TraversedTypes | V>}` | |
/** | |
* Helper type for recursively constructing paths through a type. | |
* This obsucres the internal type param TraversedTypes from exported contract. | |
* | |
* See {@link ArrayPath} | |
*/ | |
type ArrayPathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V> | |
? IsTuple<T> extends true | |
? { | |
[K in TupleKeys<T>]-?: ArrayPathImpl<K & string, T[K], TraversedTypes> | |
}[TupleKeys<T>] | |
: ArrayPathImpl<ArrayKey, V, TraversedTypes> | |
: { | |
[K in keyof T]-?: ArrayPathImpl<K & string, T[K], TraversedTypes> | |
}[keyof T] | |
/** | |
* Type which eagerly collects all paths through a type which point to an array | |
* type. | |
* @typeParam T - type which should be introspected. | |
* @example | |
* ``` | |
* Path<{foo: {bar: string[], baz: number[]}}> = 'foo.bar' | 'foo.baz' | |
* ``` | |
*/ | |
export type ArrayPath<T> = T extends any ? ArrayPathInternal<T> : never | |
/** | |
* Type to evaluate the type which the given path points to. | |
* @typeParam T - deeply nested type which is indexed by the path | |
* @typeParam P - path into the deeply nested type | |
* @example | |
* ``` | |
* PathValue<{foo: {bar: string}}, 'foo.bar'> = string | |
* PathValue<[number, string], '1'> = string | |
* ``` | |
*/ | |
export type PathValue<T, P extends Path<T> | ArrayPath<T>> = T extends any | |
? P extends `${infer K}.${infer R}` | |
? K extends keyof T | |
? R extends Path<T[K]> | |
? PathValue<T[K], R> | |
: never | |
: K extends `${ArrayKey}` | |
? T extends ReadonlyArray<infer V> | |
? PathValue<V, R & Path<V>> | |
: never | |
: never | |
: P extends keyof T | |
? T[P] | |
: P extends `${ArrayKey}` | |
? T extends ReadonlyArray<infer V> | |
? V | |
: never | |
: never | |
: never | |
export type Primitive = | |
| null | |
| undefined | |
| string | |
| number | |
| boolean | |
| symbol | |
| bigint | |
export type BrowserNativeObject = Date | FileList | File | |
/** | |
* Type which can be used to index an array or tuple type. | |
*/ | |
export type ArrayKey = number | |
export type UnknownObject = Record<string, unknown> | |
export type AnyObject = Record<string, any> | |
/** | |
* Checks whether the type is any | |
* See {@link https://stackoverflow.com/a/49928360/3406963} | |
* @typeParam T - type which may be any | |
* ``` | |
* IsAny<any> = true | |
* IsAny<string> = false | |
* ``` | |
*/ | |
export type IsAny<T> = 0 extends 1 & T ? true : false | |
/** | |
* 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 | |
* ``` | |
*/ | |
export type IsEqual<T1, T2> = T1 extends T2 | |
? (<G>() => G extends T1 ? 1 : 2) extends <G>() => G extends T2 ? 1 : 2 | |
? true | |
: false | |
: false | |
/** | |
* 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 | |
* ``` | |
*/ | |
export 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' | |
* ``` | |
*/ | |
export type TupleKeys<T extends ReadonlyArray<any>> = Exclude< | |
keyof T, | |
keyof any[] | |
> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment