Skip to content

Latest commit

 

History

History
868 lines (688 loc) · 25.9 KB

README.md

File metadata and controls

868 lines (688 loc) · 25.9 KB

ts-typedefs


npm version npm Build Status TypeScript

This library is a handy collection of TypeScript plain and generic type definitions and interfaces for both frontend and backend. You may expect zero runtime overhead if you use imported items only in type contexts.

Generated by typedoc

Credits

This project was inspired by 'ts-essentials' library. Some type names were taken from them.

Quick API review

The most convenient way to explore 'ts-typedefs' API is by easily browsing your editor's completion list that shows signatures and descriptions for selected items. Types, functions, and classes names are intended to be super descriptive and intuitive. All functional units provide typedoc documentation in comments so it is easy for IDEs to provide you with good hints.

Usage example

import { DeepPartial, FilterProps, Func, Op } from 'ts-typedefs';

class User { /* ... */ }
type UserData = FilterProps<User, Op.NotExtends<Func>>;

function updateUser(userUpd: DeepPartial<UserData>) { /* ... */ }

Provided type definitions and runtime units

Let's see them in details.

Objects

Defines an object with keys of type TKeys, and all values of TValue type.

type t0 = Obj;                      // { [key: string]: any; }     
type t1 = Obj<boolean>;             // { [key: string]: boolean; }     
type t2 = Obj<string, number>;      // { [key: number]: string;  }
type t3 = Obj<number, 'p1' | 'p2'>; // { p1: number, p2: number; }

Defines constructor function type that instantiates TInstance and accepts arguments of TArgs type.

interface User { /* ... */ }

// new (...args: any) => User
type t0 = Class<User>;

// new (...args: [string, number]) => User
type t1 = Class<User, [string, number]>;

Defines an instance type of the given class or the type of its prototype property. TClass may even be an abstract class, though those ones are not newable, but the type of their instances can be obtained through their prototype property.

import { InstanceType } from 'ts-typedefs';

class User { id!: number; } // plain class

function getAbstractUser() {
    abstract class AbstractUser {  // local abstract class, its type is not
        id!: number;               // accessible in the global namespace
    }
    return AbstractUser;
}

type t0 = InstanceType<typeof User>; // User
type t1 = InstanceType<ReturnType<typeof getAbstractUser>>; // AbstractUser

Defines a union type of all the values stored in TObj.

interface User {
    id:         number;
    login:      string | null;
    password:   string;
    isDisabled: boolean;
}
/* number | string | null | boolean */
type t0 = ValueOf<User>; 
/* union type of all properties and methods of `Array<boolean>` */
type t1 = ValueOf<boolean[]>; 

Defines the same object type as TSrcObj, but without TKeysUnion keys.

interface User {
    id:         number;
    login:      string | null;
    password:   string;
    isDisabled: boolean;
}
/* 
{ 
    id:         number; 
    login:      string | null; 
    isDisabled: boolean; 
}
*/
type t0 = RemoveKeys<User, 'password'>; 

/* { id: number; } */
type t1 = RemoveKeys<User, 'password' | 'isDisabled' | 'login'>;

Defines the same type as TObj but with particular properties filtered out according to TApproveCond. TApproveCond is a boolean operator tree structure that defines the criteria that the filtered values must match. All these operators are defined in Op namespace.

interface User {
    id:         number;
    login:      string | null;
    password:   string;
    isDisabled: boolean;
    flag:       boolean;
}

/* { login: string;  } */
type t0 = FilterProps<User, Op.Extends<string>>; 

/* 
{ 
    isDisabled: boolean; 
    flag:       boolean; 
}
*/
type t0 = FilterProps<User, Op.Extends<boolean>>; 

/* 
{ 
    isDisabled: boolean; 
    flag:       boolean; 
    id:         number; 
}
*/
type t1 = FilterProps<
    User, 
    Op.And<[
        Op.Not<Op.UnionIncludes<string>>,  // Op.UnionExcludes<> is another option
        Op.Nand<[false, true, true, true]> // this condition is always true
    ]>
>;

Because of some TypeScript limitations and bugs TApproveCond tree must be not more than 5 levels deep (number of levels limitation may change, but it can only become greater).

Defines the same object type as TSrcObj, but all values of TMappedValue type. DeepMapValues<> variant maps values for all nested objects recursively.

interface User {
    login?: string | null;
    friend: {
        friendliness: number;
        ref: User;
    }
}

/* {  login: boolean; friend: boolean; } */
type t0 = MapValues<User, boolean>;
/*
{
    login: boolean;
    friend: {
        friendliness: boolean;
        ref: DeepMapValues<User, boolean>
    }
}
*/
type t1 = DeepMapValues<User, boolean>;

Merge objects TObj1 and TObj2. Properties types from TObj2 override the ones defined on TObj1. This type is analogous to the return type of Object.assign()

interface O1 {
    p1: number;
    p2: string;
    p3: boolean;
}

interface O2 {
    p2: number | null;
    p3: string;
    p4: O1;
}

/*
{ 
    p1: number; 
    p2: number | null;
    p3: string;
    p4: O1;
}
*/
type t0 = Merge<O1, O2>;

/*
{
    p1: number;
    p2: string;
    p3: boolean;
    p4: O1;
}
*/
type t1 = Merge<O2, O1>;

Partial/Required<TObj, TKeys = keyof TObj> defines the same type as TObj but with all TKeys made optional/required.

DeepPartial/Required<> defines the same type as TObj but with all properties made recursively Partial/Required<>.

This two types are actually exactly opposite to each other.

interface User {
    id: number;
    name: {
        first: string;
    }
}

/*
{
    id?:   undefined | number;
    name?: undefined | {
        first: string;
    }
}
*/
type PartUser = Partial<User, /* TKeys = keyof User */>;

/*
{
    id?:     number | undefined;
    name?:   undefined | {
        first?: string | undefined;
    };
}
*/
type DeepPartUser = DeepPartial<User>;

type RequUser     = Required<User, /* TKeys = keyof User */>; // User
type DeepRequUser = DeepRequired<DeepPartUser>; // User

Readonly/Mutable<TObj, TKeys = keyof TObj> defines the same type as TObj but with all TKeys made readonly/mutable.

DeepReadonly/Mutable<> defines the same type as TObj but with all properties made recursively Readonly/Mutable<>.

This two types are actually exactly opposite to each other.

interface User {
    id: number;
    name: {
        first: string;
    }
}

/*
{
    readonly id: number;
    readonly name: {
        first: string;
    }
}
*/
type RoUser = Readonly<User, /* TKeys = keyof User */>;

/*
{
    readonly id: number;
    readonly name: {
        readonly first: string;
    };
}
*/
type DeepRoUser = DeepReadonly<User>;

type MutUser     = Mutable<RoUser, /* TKeys = keyof User */>; // User
type DeepMutUser = DeepMutable<DeepRoUser>; // User

DeepReadonly<> is quite handy when you define deep readonly multidimensional arrays.

type t0 = DeepReadonly<number[][][]>;
// readonly (readonly (readonly number[])[])[]

Defines the same type as TObj, but adds 'optional' modifier ? to all properties that allow undefined as their value type (this includes unknown and any).

interface User {
    bio: string | undefined;
    secret: unknown;
    name: string;
}

/*
{
    bio?: string | undefined;
    secret?: unknown;  
    name: string;     // notice may-not-be `undefined` props don't get '?' modifier
}
*/
type RepairedUser = OptionalLikelyUndefProps<User>;

Functions

Defines a Function subtype with the given arguments, return type and this context. If it is AsyncFunc<> TRetval is packed into Promise<TRetval>

interface User { /* ... */ }

// (this: any, ...args: any) => unknown
type t0 = Func;                               

// (this: any, ...args: [string, number | undefined]) => unknown
type t1 = Func<[string, number | undefined]>;

// (this: any, ...args: [boolean]) => void
type t2 = Func<[boolean], void>;

// (this: User,    ...args: [boolean]) => number
type t3 = Func<[boolean], number, User>;

// (this: any, ...args: [string]) => Promise<User>
type t4 = AsyncFunc<[string], User>

Defines the unpacked result type of the Promise returned by the specified TAsyncFunc.

class User {
    static async getById(id: number): Promise<User> {
        // ...
    }
}

// User
type t0 = AsyncFuncReturnType<AsyncFunc<[number], User>>;

// User
type t1 = AsyncFuncReturnType<typeof User.getById>

Decorators

Defines a static or instance method decorator function type. TArgs tuple type limits the arguments' type decorated method accepts, TRetval limits the return type of the decorated method. TMethNameLimit defines the limitation for method names this decorator may be applied to.

declare function decor_any(): MethodDecorator;
declare function decor_bool(): MethodDecorator<[boolean]>;
declare function decor_getId(): MethodDecorator<any[], any, 'getId'>;

function decor_str$num_bool(): MethodDecorator<[string, number], boolean> {
    // argument types are automatically deduced and strongly typed here
    return (protoOrClass, methodName, propDescriptor) => {
        /* (this: typeof protoOrClass, ...args: [string, number]) => boolean */
        const val = propDescriptor.value;
        // ...
    };
};

class User {
    @decor_getId()        // compile error (method name mismatch)
    @decor_bool()         // compile error (params type mismatch)
    @decor_str$num_bool() // compile error (params and return type mismatch)
    @decor_any()          // works fine
    meth0(bol: boolean, num: number): void {}

    @decor_any()  // works fine
    @decor_bool() // works fine
    meth1(bol: boolean) {
        return bol ? 32 : 'kek';
    }

    @decor_any()          // works fine
    @decor_str$num_bool() // works fine
    meth2(str: string, num: number) {
        return !!str && !!num;
    }

    @decor_getId() // works fine
    getId() { }
}

Defines a static or instance property decorator function type.

declare function decorateAny(): PropertyDecorator;
function decorateStr(): PropertyDecorator<string> {
    return /* function arguments are analogously deduced */;
};
function decorIdProp(): PropertyDecorator<number, 'id' | '_id'> {
    return /* function arguments are analogously deduced */;
};


export class User {
    @decorIdProp() // compile error (prop name and value type mismatch)
    @decorateStr() // works fine
    @decorateAny() // works fine
    prop0!: string;

    @decorIdProp() // compile error (prop name mismatch)
    @decorateStr() // compile error (prop value type mismatch)
    @decorateAny() // works fine
    prop1!: number;

    @decorIdProp() // works fine
    @decorateStr() // compile error (prop value type mismatch)
    @decorateAny() // works fine
    _id!: number;

    @decorIdProp() // compile error (prop value type mismatch)
    @decorateStr() // works fine
    @decorateAny() // works fine
    id!: string;
}

Logical

Sequentially performs the following logic:

Expands to TIfTrue if TCond extends true.

Expands to TElse if TCond extends false.

Expands to TIfCondIsBool if TCond extends boolean.

As a convention, enclose TCond argument in parens.

type t0 = If<(true), number, string>;            // number
type t1 = If<(false), number, string>;           // string
type t2 = If<(boolean), number, string, bigint>; // bigint

type t3 = If<(And<[NotExtends<22, number>, true, true]>),
    string,
    If<(false),  // nested condition
        number, 
        string
>>; // string

// You may use leading ampersand or pipe in order to explicitly separate branches visually
type t4 = If<(true)
    | number, // you may use & instead of |

    | string
>; // number

Defines false unit type if T extends true. Defines true unit type if T extends false. Defines TIfTIsBool when T is exactly boolean type.

type t0 = Not<true>;            // false
type t1 = Not<false>;           // true  
type t2 = Not<boolean, number>; // number
type t3 = Not<Not<true>>;       // true

Defines true or false accroding to the definition of and/nand(negated and) logical operator. It gets applied to all the argumets in the given tuple type T.

type t0 = And<[true, true, true]>; // true
type t1 = And<true[]>;             // true
type t2 = And<[true, false, true]> // false

type t3 = And<boolean[]>;          // false
type t4 = And<[boolean, true]>;    // false

type t5 = Nand<[true, true]>;      // false

Defines true or false accroding to the definition of or/nor(negated or) logical operator. It gets applied to all the argumets in the given tuple type T.

type t0 = Or<[false, false, false]>; // false
type t1 = Or<false[]>;               // false
type t2 = Or<[false, true, false]>   // true

type t3 = Or<boolean[]>;             // true
type t4 = Or<[boolean, false]>;      // true

type t5 = Nor<[true, true]>;         // false

Defines true if TExtender is assignable to TExtendee, otherwise false.

It verifies that you may physically assign a value of type TExtender to TExtendee. That's why union types with excess members that are not assignable to TExtendee will evaluate to false.

type t0 = Extends<string | null, string>; // false
type t1 = Extends<true, boolean>;         // true
type t2 = Extends<never, never>;          // true

type t3 = NotExtends<22, number>;         // false

Defines true if T1 is exactly T2, false otherwise. Even AreSame<unknown, any> expands to false. Only the same types expand to true.

It doesn't tolerate co/bi/contravaraince, only the types of exactly the same shapes (excluding function types limitation) will cause to return true.

Beware that this type works as vanilla extends clause with function types, so comparing functions is not that strict.

type t0 = AreSame<{}, { num: number }>; // false
type t1 = AreSame<
    { num: number, str: string }, 
    { num: number, str: string }
>; // true
type t2 = AreSame<any, unknown>;        // false   
type t8 = AreSame<Func, Func>;          // true
type t9 = AreSame<[number], [number]>;  // true
type t10 = AreSame<[number, string], [number]>; // false

Defines true[false] if TSuspect is exactly of any type, false[true] otherwise.

type t0 = IsAny<any>;        // true
type t1 = IsAny<unknown>;    // false
type t2 = IsAny<never>;      // false
type t3 = IsAny<string>;     // false

type t4 = IsNotAny<any>;     // false
type t5 = IsNotAny<unknown>; // true
// ...

Defines true[false] if TSuspect is exactly of unknown type, false[true] otherwise.

type t0 = IsUnknown<unknown>;  // true
type t1 = IsUnknown<boolean>;  // false

type t2 = IsNotUnknown<never>; // true
// ...

Runtime

C++ style operator, a syntactic sugar for writing casts like value as any as T when a simple value as T cast cannot be performed. Use it with caution!

This function is actually noop at runtime, all it does is it suppresses 'inability to cast' tsc error. It's better to use this function rather than value as any as T cast, because it amplifies your attention to such uneven places in code and it may be easier to do a Ctrl + F search for these.

interface User {
 // ...
}
type UserUpdate = DeepPartial<RemoveKeys<User, 'password'>>;

const userUpd: UserUpdate = // ...

Object.assign(userUpd, { 
    password: 'somepassword-pfff', otherRequiredFields: // ...
});

// For future devs: reinterpreting here, because userUpd has the same shape as `User`
let user = reinterpret<User>(userUpd); 

// `typeof user` is `User` 

Class used to perform never type value checks in unreachable code.

const val: string | number;


if (typeof val === 'string') {
     return null;
} else if (typeof val === 'number') {
     throw new Debug.UnreachableCodeError(val); // compiler error: val is not of type `never` here
     return;
} else {
     throw new Debug.UnreachableCodeError(val); // this is ok val has `never` type here
}

enum Enum {
    A, B, C
}
let suspect: Enum = // ...
switch (suspect) {
    case Enum.A: return;
    case Enum.B: return;
    default: {
        // compiler error, this path is reachable
        // as we didn't handle `suspect === Enum.C` case
        throw new Debug.UnreachableCodeError(suspect);
    }
}

Misc

Defines nominal type by adding a property with TTagName value to TTarget. TTagName must be unique across your application, treat it like the name of your nominal type.

With this type, you may pick particular subclass of values from the given type and force your clients to filter other values that are assignable to TTarget but don't obey to your prerequisites, thus making them pay more attention to them.

type PositiveInt = Tag<number, 'PositiveInt'>;
type CsvString   = Tag<string, 'CsvString'>;

// Prerequisites: `userId > 0`
async function getUser(userId: PositiveInt) {
     return userRepository.findById(userId);
}

// Prerequisites: given string must be valid csv
function parseCsv(csvString: CsvString) {
     // Here you may be sure that client payed attention to checking the input

     const lines = csvString.split('\n').map(line => line.split(','));
}

getUser(-2);                // compile error
getUser(58);                // compile error
getUser(58 as PositiveInt); // fine (explicit cast pays your attention to prerequisites)

parseCsv('\nbla bla');      // compile error
parseCsv('a,b,c\nd,e,f' as CsvString);   // fine

Defines an intersection type of all union's items.

Because of TypeScript boolean representation as type boolean = true | false you get the following result: UnionToIntersection<boolean> is true & false

// string & number & number[]
type t0 = UnionToIntersection<string | number | number[]>;

Defines the type of value, which is passed to TPromise.then(cb) cb callback.

type t0 = UnpackPromise<Promise<number>>; // number;
type t1 = UnpackPromise<Promise<void>>;   // void;

// Promise<string>;
type t2 = UnpackPromise<
    Promise<Promise<string>>
>; 

// string
type t3 = UnpackPromise<UnpackPromise<
    Promise<Promise<string>>
>>;

Shorthand for Partial/Required/Readonly/Mutable/NullableProps<Pick<TObj, TKeys>>, but better optimized (into one mapped object type).

interface User {
    readonly id: number;
    name?: string;
    bio: string;
}
PickAsOptional<User, 'name' | 'id'> === Partial<Pick<User, 'name' | 'id'>>
PickAsRequired<User, 'name' | 'id'> === Required<Pick<User, 'name' | 'id'>>
PickAsReadonly<User, 'name' | 'id'> === Readonly<Pick<User, 'name' | 'id'>>

// ...

Nullable<T> defines type T that may also be null or undefined:

export type Nullable<T> = T | undefined | null;

DeepNullable<T> defines the same type as TObj but with all properties made NonNullable<> recursively.

interface User {
    id: number;
    name: {
        first: string;
        last:  string;
    };
}

/*
Nullable<{
    id?:  Nullable<number>;
    name?: Nullable<{
        first?: Nullable<string>;
        last?:  Nullable<string>;
    }>;
}>
*/
type t0 = DeepNullable<User>;

Defines a union of all possible value types defined in the language, null and undefined are considered to be primitive types.

export type Primitive = (
    | number 
    | string 
    | boolean  
    | undefined 
    | symbol 
    | null
    | bigint
);

Defines a union of all possible strings retuned by applying typeof operator.

export type TypeName = (
    | 'number'    
    | 'string' 
    | 'boolean'  
    | 'undefined' 
    | 'object' 
    | 'function' 
    | 'symbol'
    | 'bigint'
);