UNPKG

@equantic/linq

Version:
177 lines (176 loc) 7.34 kB
export type IndexedObject = Object & { [key: string]: any; }; export interface AbstractType<T> extends Function { prototype: T; } export type Constructor<T> = new (...args: Array<any>) => T; export type Serializable<T> = Constructor<T> | AbstractType<T>; export type Primitive = null | undefined | string | number | boolean | symbol | bigint; export type BrowserNativeObject = Date | FileList | File; export type FieldValues = Record<string, any>; /** * 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; /** * 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; /** * Type which can be used to index an array or tuple type. */ export type ArrayKey = number; /** * 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[]>; /** * 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}` : 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; /** * See {@link Path} */ export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>; /** * 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 obscures 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; /** * See {@link ArrayPath} */ export type FieldArrayPath<TFieldValues extends FieldValues> = ArrayPath<TFieldValues>; /** * 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; /** * See {@link PathValue} */ export type FieldPathValue<TFieldValues extends FieldValues, TFieldPath extends FieldPath<TFieldValues>> = PathValue<TFieldValues, TFieldPath>; /** * See {@link PathValue} */ export type FieldArrayPathValue<TFieldValues extends FieldValues, TFieldArrayPath extends FieldArrayPath<TFieldValues>> = PathValue<TFieldValues, TFieldArrayPath>; /** * Type to evaluate the type which the given paths point to. * @typeParam TFieldValues - field values which are indexed by the paths * @typeParam TPath - paths into the deeply nested field values * @example * ``` * FieldPathValues<{foo: {bar: string}}, ['foo', 'foo.bar']> * = [{bar: string}, string] * ``` */ export type FieldPathValues<TFieldValues extends FieldValues, TPath extends FieldPath<TFieldValues>[] | readonly FieldPath<TFieldValues>[]> = {} & { [K in keyof TPath]: FieldPathValue<TFieldValues, TPath[K] & FieldPath<TFieldValues>>; }; /** * Type which eagerly collects all paths through a fieldType that matches a give type * @typeParam TFieldValues - field values which are indexed by the paths * @typeParam TValue - the value you want to match into each type * @example * ```typescript * FieldPathByValue<{foo: {bar: number}, baz: number, bar: string}, number> * = 'foo.bar' | 'baz' * ``` */ export type FieldPathByValue<TFieldValues extends FieldValues, TValue> = { [Key in FieldPath<TFieldValues>]: FieldPathValue<TFieldValues, Key> extends TValue ? Key : never; }[FieldPath<TFieldValues>]; export {};