@skyroc/type-utils
Version:
Advanced TypeScript utility types for form handling, path manipulation, and type transformations
187 lines • 9.6 kB
text/typescript
//#region src/fn.d.ts
type Fn = (...args: any[]) => any;
type Noop = () => void;
/**
* - get all the function types in an object
* @example
* interface Foo {
* a: number;
* b?: string;
* c(): void;
* d: (x: number) => string;
* e?: () => void; // optional also can be recognized
* }
* type FooFnTypes = {
* c: () => void;
* d: (x: number) => string;
* e?: (() => void) | undefined;
}
*/
type OnlyFunctions<T> = { [K in keyof T as NonNullable<T[K]> extends Fn ? K : never]: T[K] };
/**
* - get all the function keys in an object
* @example
* interface Foo {
* a: number;
* b?: string;
* c(): void;
* d: (x: number) => string;
* e?: () => void;
* }
* type FooFnKeys = FunctionKeys<Foo>; // 'c' | 'd' | 'e'
*/
type FunctionKeys<T> = { [K in keyof T]?: NonNullable<T[K]> extends Fn ? K : never }[keyof T];
/**
* - get all the function types in an object
* @example
* interface Foo {
* a: number;
* b?: string;
* c(): void;
* d: (x: number) => string;
* e?: () => void;
* }
* type FooFnTypes = FunctionUnion<Foo>; // (() => void) | ((x:number)=>string) | (()=>void)
*/
type FunctionUnion<T> = { [K in keyof T]: NonNullable<T[K]> extends Fn ? T[K] : never }[keyof T];
//#endregion
//#region src/form.d.ts
type CustomElement<T = any> = Partial<HTMLElement> & T & {
checked?: boolean;
disabled?: boolean;
files?: FileList | null;
focus?: Noop;
options?: HTMLOptionsCollection;
type?: string;
value?: any;
};
type FieldElement<T = any> = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | CustomElement<T>;
//#endregion
//#region src/utility-types.d.ts
/**
* DeepPartial<T>
* @example
* type User = {
* name: string;
* age: number;
* address: {
* city: string;
* street: string;
* };
* };
* type PartialUser = DeepPartial<User>;
* // => { name?: string; age?: number; address?: { city?: string; street?: string } }
*/
type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? (T[K] extends ((...args: any[]) => any) ? T[K] : DeepPartial<T[K]>) : T[K] };
//#endregion
//#region src/utils.d.ts
/**
* @description
* without changing the type’s semantics, it only prettifies the IDE display—flattening the shape so hints are easier to read.
* @example
* type Raw = { a: number } & { b: string };
* type Pretty = Prettify<Raw>;
* // => { a: number; b: string; }
*/
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type Primitive = string | number | boolean | bigint | symbol | null | undefined | Date | Function;
type Join<P extends string, K extends string | number> = P extends '' ? `${K}` : `${P}.${K}`;
/**
* @example
* type FormValues = {
* age: number;
* code: string;
* info: { age: number; city: string; name: string };
* info2: { age: number; city: string; name: string }[];
* phone: string;
* };
* type P1 = LeafPaths<FormValues>;
* // => "age" | "code" | "phone" | "info.age" | "info.city" | "info.name" | `info2.${number}.age` | `info2.${number}.city` | `info2.${number}.name`
*/
type LeafPaths<T, P extends string = ''> = T extends Primitive ? P extends '' ? never : P : T extends readonly (infer U)[] ? LeafPaths<U, Join<P, number>> : T extends object ? { [K in Extract<keyof T, string>]: LeafPaths<T[K], Join<P, K>> }[Extract<keyof T, string>] : never;
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...0[]];
/**
* @example
* type FormValues = {
* age: number;
* code: string;
* info: { age: number; city: string; name: string; pl: { age: number } };
* info2: { age: number; city: string; name: string }[];
* phone: string;
* };
* type P1 = AllPaths<FormValues>;
* // => "age" | "code" | "phone"|"info" | "info.age" | "info.city" | "info.name" | "info.pl" | "info.pl.age" | `info2.${number}.age` | `info2.${number}.city` | `info2.${number}.name`
*/
type AllPaths<T, Index extends number = number, P extends string = '', Depth extends number = 6> = [Depth] extends [never] ? never : T extends Primitive ? P extends '' ? never : P : T extends readonly (infer U)[] ? (P extends '' ? never : P) | AllPaths<U, Index, Join<P, `${Index}`>, Prev[Depth]> : T extends object ? (P extends '' ? never : P) | { [K in Extract<keyof T, string>]: AllPaths<T[K], Index, Join<P, K>, Prev[Depth]> }[Extract<keyof T, string>] : never;
type OptionalIfObject<T> = T extends object ? (T extends readonly any[] ? T : { [K in keyof T]?: T[K] }) : T;
type DeepOptionalIfObject<T> = T extends object ? T extends readonly any[] ? T : { [K in keyof T]: DeepOptionalIfObject<T[K]> } : T;
type PathValue<T, P extends string> = P extends `${infer K}.${infer R}` ? K extends `${number}` | 'number' ? T extends readonly (infer U)[] ? PathValue<U, R> : any : K extends keyof T ? PathValue<T[K], R> : any : P extends `${infer K}` ? K extends `${number}` | 'number' ? T extends readonly (infer U)[] ? U : any : K extends keyof T ? T[K] : any : any;
/**
* @example
* type FormValues = {
* age: number;
* code: string;
* info: { age: number; city: string; name: string; pl: { age: number } };
* info2: { age: number; city: string; name: string }[];
* phone: string;
* };
* type P1 = PathToType<FormValues, 'age'>; // number
* type P2 = PathToType<FormValues, 'info'>; // { age?: number; city?: string; name?: string; pl?: { city: string; name: string } | undefined }
* type P3 = PathToType<FormValues, 'info.pl'>; // { city?: string; name?: string } | undefined
* type P4 = PathToType<FormValues, 'info2.number'>; // { age?: number; city?: string; name?: string } ← 数组元素对象被可选化
* type P5 = PathToType<FormValues, 'info2.0.age'>; // number
*/
type PathToType<T, P extends string> = OptionalIfObject<PathValue<T, P>>;
/**
* @example
* type FormValues = {
* age: number;
* code: string;
* info: { age: number; city: string; name: string; pl: { age: number } };
* info2: { age: number; city: string; name: string }[];
* phone: string;
* };
* type P1 = PathToDeepType<FormValues, 'info'>;
* // => { age?: number; city?: string; name?: string; pl?: { age?: number } | undefined }
*/
type PathToDeepType<T, P extends string> = DeepOptionalIfObject<PathValue<T, P>>;
type DeepOptional<T> = T extends Primitive ? T : T extends readonly (infer U)[] ? readonly DeepOptional<U>[] : { [K in keyof T]?: DeepOptional<T[K]> };
type Wrap<K extends string, V> = { [P in K]: V };
type IsNumSeg<S extends string> = S extends `${number}` | 'number' ? true : false;
type BuildShape<T, P extends string> = P extends `${infer K}.${infer R}` ? K extends keyof T ? T[K] extends readonly (infer U)[] ? R extends `${infer I}.${infer R2}` ? IsNumSeg<I> extends true ? Wrap<Extract<K, string>, Array<BuildShape<U, R2> extends infer S ? (S extends object ? S : never) : never>> : never : Wrap<Extract<K, string>, Array<BuildShape<U, R> extends infer S ? (S extends object ? S : never) : never>> : Wrap<Extract<K, string>, BuildShape<T[K], R>> : never : P extends `${infer K}` ? K extends keyof T ? T[K] extends readonly (infer U)[] ? Wrap<Extract<K, string>, Array<U extends object ? DeepOptional<U> : U>> : T[K] extends object ? Wrap<Extract<K, string>, DeepOptional<T[K]>> : Wrap<Extract<K, string>, T[K]> : never : never;
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
type MergeUnion<U> = Prettify<{ [K in keyof UnionToIntersection<U>]: UnionToIntersection<U>[K] }>;
/**
* @example
* type FormValues = {
* age: number;
* code: string;
* info: { age: number; city: string; name: string };
* info2: { age: number; city: string; name: string }[];
* phone: string;
* };
* type P1 = ShapeFromPaths<FormValues, ['age','info','info2.2.city']>; // { age:number; info:{...?}; info2:{city?:string}[] }
*/
type ShapeFromPaths<T, Ps extends readonly string[]> = Ps extends never[] | [] ? T : MergeUnion<Ps[number] extends infer P ? (P extends string ? BuildShape<T, P> : never) : never>;
type PathsShape<T, Index extends number = number, P extends string = '', Depth extends number = 6> = [Depth] extends [never] ? never : T extends Primitive ? P extends '' ? {} : { [K in P]: true } : T extends readonly (infer U)[] ? (P extends '' ? {} : { [K in P]: true }) & PathsShape<U, Index, Join<P, `${Index}`>, Prev[Depth]> : T extends object ? (P extends '' ? {} : { [K in P]: true }) & MergeUnion<{ [K in Extract<keyof T, string>]: PathsShape<T[K], Index, Join<P, K>, Prev[Depth]> }[Extract<keyof T, string>]> : {};
type AllPathsShape<T> = MergeUnion<PathsShape<T>>;
type AllPathsKeys<T> = keyof AllPathsShape<T> & string;
type KeyToNestedObject<K extends string, V> = K extends `${infer Head}.${infer Rest}` ? { [P in Head]: KeyToNestedObject<Rest, V> } : { [P in K]: V };
type Merge<T> = { [K in keyof T]: T[K] };
/**
* @example
* type Inputs = {
* password: string;
* username: string;
* numbers: number[];
* users: {
* age: number;
* name: string;
* }[];
* };
* type P1 = ArrayKeys<Inputs>; // "numbers" | "users"
*/
type ArrayKeys<T> = { [K in keyof T]: T[K] extends readonly any[] ? K : never }[keyof T] extends never ? any : { [K in keyof T]: T[K] extends readonly any[] ? K : never }[keyof T];
type ArrayElementValue<T, K extends keyof T> = T[K] extends (infer U)[] ? U : any;
//#endregion
export { AllPaths, AllPathsKeys, AllPathsShape, ArrayElementValue, ArrayKeys, CustomElement, DeepPartial, FieldElement, Fn, FunctionKeys, FunctionUnion, KeyToNestedObject, LeafPaths, Merge, MergeUnion, Noop, OnlyFunctions, PathToDeepType, PathToType, Prettify, Primitive, ShapeFromPaths, Wrap };