ts-essentials
All essential TypeScript types in one place π€
Install
npm install --save-dev ts-essentials
π We require typescript>=4.1
. If you're looking for support for older TS versions, please have a look at the
TypeScript dependency table
π As we really want types to be stricter, we require enabled
strictNullChecks in your project
If you use any functions you should add
ts-essentials
to your dependencies
(npm install --save ts-essentials
) to avoid runtime errors in production.
What's inside?
ts-essentials
is a set of high-quality, useful TypeScript types that make writing type-safe code easier.
Basic
Primitive
type matching all primitive values.noop
function that takes any arguments and returns nothing, as a placeholder for e.g. callbacks.
Dictionaries
keywords: map
const stringDict: Dictionary<string> = {
a: "A",
b: "B",
};
const dictOfNumbers: Dictionary<string, number> = {
420: "four twenty",
1337: "HAX",
};
export type DummyOptions = "open" | "closed" | "unknown";
const dictFromUnionType: Dictionary<number, DummyOptions> = {
closed: 1,
open: 2,
unknown: 3,
};
type stringDictValues = DictionaryValues<typeof stringDict>;
const safeDict: SafeDictionary<number> = {};
const value: number | undefined = safeDict["foo"];
type ConfigKeys = "LOGLEVEL" | "PORT" | "DEBUG";
const configSafeDict: SafeDictionary<number, ConfigKeys> = {
LOGLEVEL: 2,
};
const maybePort: number | undefined = configSafeDict["PORT"];
const configDict: Dictionary<number, ConfigKeys> = {
LOGLEVEL: 2,
PORT: 8080,
DEBUG: 1,
};
const port: number = configDict["PORT"];
Type checkers
IsUnknown
checks whether we get unknown
or not. If so, we get true
. Otherwise, false
type Test1 = IsUnknown<unknown>;
type Test2 = IsUnknown<{ name: "Alexey" }>;
IsNever
checks whether we get never
or not. If so, we get true
. Otherwise, false
type Test1 = IsNever<never>;
type Test2 = IsNever<{ name: "Alexey" }>;
Deep* wrapper types
- DeepPartial
- DeepRequired
- DeepReadonly
- DeepNonNullable
- DeepNullable
- DeepUndefinable
keywords: recursive, nested, optional
type ComplexObject = {
simple: number;
nested: {
a: string;
array: [{ bar: number }];
};
};
type ComplexObjectPartial = DeepPartial<ComplexObject>;
const samplePartial: ComplexObjectPartial = {
nested: {
array: [{}],
},
};
type ComplexObjectAgain = DeepRequired<ComplexObjectPartial>;
const sampleRequired: ComplexObjectAgain = {
simple: 5,
nested: {
a: "test",
array: [{ bar: 1 }],
},
};
type ComplexObjectReadonly = DeepReadonly<ComplexObject>;
type ComplexNullableObject = {
simple: number | null | undefined;
nested: {
a: string | null | undefined;
array: [{ bar: number | null | undefined }] | null | undefined;
};
};
type ComplexObjectNonNullable = DeepNonNullable<ComplexNullableObject>;
const sampleNonNullable: ComplexObjectNonNullable = {
simple: 5,
nested: {
a: "test",
array: [{ bar: null }],
},
};
type ComplexObjectNullable = DeepNullable<ComplexObject>;
const sampleDeepNullable1: ComplexObjectNullable = {
simple: null,
nested: {
a: null,
array: [{ bar: null }],
},
};
const sampleDeepNullable2: ComplexObjectNullable = {
simple: 1,
nested: {
array: [null],
},
};
declare function tryGet(name: string): string | undefined;
type ComplexObjectUndefinable = DeepUndefinable<ComplexObject>;
const sampleDeepUndefinable1: ComplexObjectUndefinable = {
simple: undefined,
nested: {
a: tryGet("a-value"),
array: [{ bar: tryGet("bar-value") }],
},
};
const sampleDeepUndefinable2: ComplexObjectUndefinable = {
nested: {
array: [[{ bar: undefined }]],
},
};
Difference between DeepRequired
and DeepNonNullable
DeepRequired
is closer to Required
but DeepNonNullable
on the other hand is closer to NonNullable
It means that DeepRequired
doesn't remove null
and undefined
but only makes fields required. On the other hand,
DeepNonNullable
will only remove null
and undefined
but doesn't prohibit the field to be optional.
Let's have a look at the optional nullable field:
type Person = {
name?: string | null | undefined;
};
type NonNullablePerson = DeepNonNullable<Person>;
type RequiredPerson = DeepRequired<Person>;
Let's have a look at the required nullable field:
type FullName = {
first: string | null | undefined;
};
type NonNullableFullName = DeepNonNullable<FullName>;
type RequiredFullName = DeepRequired<FullName>;
And there's no difference between DeepNonNullable
and DeepRequired
if the property is non nullable and required
Writable
Make all attributes of object writable.
type Foo = {
readonly a: number;
readonly b: string;
};
const foo: Foo = { a: 1, b: "b" };
(foo as Writable<typeof foo>).a = 42;
type Foo = {
readonly foo: string;
bar: {
readonly x: number;
};
}[];
const test: DeepWritable<Foo> = [
{
foo: "a",
bar: {
x: 5,
},
},
];
test[0].foo = "b";
test[0].bar.x = 2;
Buildable
keywords: builder
A combination of both DeepWritable
and DeepPartial
. This type allows building an object step-by-step by assigning
values to its attributes in multiple statements.
interface ReadonlyObject
extends Readonly<{
simple: number;
nested: Readonly<{
a: string;
array: ReadonlyArray<Readonly<{ bar: number }>>;
}>;
}> {}
const buildable: Buildable<ReadonlyObject> = {};
buildable.simple = 7;
buildable.nested = {};
buildable.nested.a = "test";
buildable.nested.array = [];
buildable.nested.array.push({ bar: 1 });
const finished = buildable as ReadonlyObject;
Omit
Our version of Omit
is renamed to StrictOmit
in v3
, since the builtin Omit
has become part of TypeScript 3.5
StrictOmit
Usage is similar to the builtin version, but checks the filter type more strictly.
type ComplexObject = {
simple: number;
nested: {
a: string;
array: [{ bar: number }];
};
};
type SimplifiedComplexObject = StrictOmit<ComplexObject, "nested">;
type SimplifiedComplexObject = StrictOmit<ComplexObject, "nested" | "simple">;
Comparison between Omit
and StrictOmit
Following the code above, we can compare the behavior of Omit
and StrictOmit
.
type SimplifiedComplexObjectWithStrictOmit = StrictOmit<ComplexObject, "nested" | "simple" | "nonexistent">;
type SimplifiedComplexObjectWithOmit = Omit<ComplexObject, "nested" | "simple" | "nonexistent">;
As is shown in the example, StrictOmit
ensures that no extra key is specified in the filter.
Usage is similar to the builtin version, but checks the filter type more strictly.
interface Dog {
type: "dog";
woof(): void;
}
interface Cat {
type: "cat";
meow(): void;
}
interface Mouse {
type: "mouse";
squeak(): void;
}
type Animal = Dog | Cat | Mouse;
type DogAnimal = StrictExtract<Animal, { type: "dog" }>;
type HouseAnimal = StrictExtract<Animal, { type: "dog" | "cat" }>;
Comparison between Extract
and StrictExtract
Following the code above, we can compare the behavior of Extract
and StrictExtract
.
type HouseAnimalWithStrictExtract = StrictExtract<Animal, { type: "dog" | "cat" | "horse" }>;
type HouseAnimalWithExtract = Extract<Animal, { type: "dog" | "cat" | "horse" }>;
DeepOmit
Recursively omit deep properties according to key names.
Here is the Teacher
interface.
interface Teacher {
name: string;
gender: string;
students: { name: string; score: number }[];
}
Now suppose you want to omit gender
property of Teacher
, and score
property of students
. You can achieve this
with a simple type filter.
In the filter, the properties to be omitted completely should be defined as never
. For the properties you want to
partially omit, you should recursively define the sub-properties to be omitted.
type TeacherSimple = DeepOmit<
Teacher,
{
gender: never;
students: {
score: never;
};
}
>;
NOTE
DeepOmit
works fine with Array
s and Set
s. When applied to a Map
, the filter is only applied to its value.- If there exists any property in the filter which is not in the original type, an error will occur.
OmitProperties
keywords: filter, props
Removes all properties extending type P
in type T
. NOTE: it works opposite to filtering.
interface Example {
log(): void;
version: string;
}
type ExampleWithoutMethods = OmitProperties<Example, Function>;
type ExampleWithoutMethods = OmitProperties<Example, Function | string>;
PickProperties
Pick only properties extending type P
in type T
.
interface Example {
log(): void;
version: string;
versionNumber: number;
}
type ExampleOnlyMethods = PickProperties<Example, Function>;
type ExampleOnlyMethodsAndString = PickProperties<Example, Function | string>;
NonNever
Useful for purifying object types. It improves intellisense but also allows for extracting keys satisfying a conditional
type.
type GetDefined<TypesMap extends { [key: string]: any }> = keyof NonNever<
{ [T in keyof TypesMap]: TypesMap[T] extends undefined ? never : TypesMap[T] }
>;
NonEmptyObject
Useful for accepting only objects with keys, great after a filter like OmitProperties or PickProperties.
type NumberDictionary<T> = NonEmptyObject<PickProperties<T, number>>;
type SomeObject = NumberDictionary<{ a: number; b: string }>;
type EmptyObject = NumberDictionary<{}>;
Merge
keywords: override
type Foo = {
a: number;
b: string;
};
type Bar = {
b: number;
};
const xyz: Merge<Foo, Bar> = { a: 4, b: 2 };
MergeN
keywords: override
type Tuple = [
{
a: number;
b: string;
},
{
b: number;
},
];
const xyz: MergeN<Tuple> = { a: 4, b: 2 };
MarkRequired
Useful when you're sure some optional properties will be set. A real life example: when selecting an object with its
related entities from an ORM.
class User {
id: number;
posts?: Post[];
photos?: Photo[];
}
type UserWithPosts = MarkRequired<User, "posts">;
async function getUserWithPosts(id: number): Promise<UserWithPosts> {
return userRepo.findOneOrFail({ id }, { relations: ["posts"] }) as Promise<UserWithPosts>;
}
MarkOptional
Useful when you want to make some properties optional without creating a separate type.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserWithoutPassword = MarkOptional<User, "password">;
ReadonlyKeys
Gets keys of an object which are readonly.
type T = {
readonly a: number;
b: string;
};
type Result = ReadonlyKeys<T>;
WritableKeys
Gets keys of an object which are writable.
type T = {
readonly a: number;
b: string;
};
type Result = WritableKeys<T>;
OptionalKeys
Gets keys of an object which are optional.
type T = {
a: number;
b?: string;
c: string | undefined;
d?: string;
};
type Result = OptionalKeys<T>;
RequiredKeys
Gets keys of an object which are required.
type T = {
a: number;
b?: string;
c: string | undefined;
d?: string;
};
type Result = RequiredKeys<T>;
PickKeys
Gets keys of properties of given type in object type.
type T = {
a: number;
b?: string;
c: string | undefined;
d: string;
};
type Result1 = PickKeys<T, string>;
type Result2 = PickKeys<T, string | undefined>;
UnionToIntersection
Useful for converting mapped types with function values to intersection type (so in this case - overloaded function).
type Foo = {
bar: string;
xyz: number;
};
type Fn = UnionToIntersection<{ [K in keyof Foo]: (type: K, arg: Foo[K]) => any }[keyof Foo]>;
Opaque types
Opaque types allow you to create unique type that can't be assigned to base type by accident. Good examples of opaque
types include:
- JWTs or other tokens - these are special kinds of string used for authorization purposes. If your app uses multiple
types of tokens each should be a separate opaque type to avoid confusion.
- specific currencies - amount of different currencies shouldn't be mixed
- bitcoin address - special kind of string
It's critical to understand that each token (second argument to Opaque
) has to be unique across your codebase.
We encourage you to leverage a pattern where you have single function to validate base type and create opaque type.
type PositiveNumber = Opaque<number, "PositiveNumber">;
function makePositiveNumber(n: number): PositiveNumber {
if (n <= 0) {
throw new Error(`Value ${n} is not positive !`);
}
return n as PositiveNumber;
}
type NegativeNumber = Opaque<number, "NegativeNumber">;
function makeNegativeNumber(n: number): NegativeNumber {
if (n >= 0) {
throw new Error(`Value ${n} is not negative !`);
}
return n as NegativeNumber;
}
let a = makePositiveNumber(5);
let b = makeNegativeNumber(-10);
a = b;
Tuple constraint
function foo<T extends Tuple>(tuple: T): T {
return tuple;
}
const ret = foo(["s", 1]);
You can also parametrize Tuple
type with a type argument to constraint it to certain types, i.e.
Tuple<string | number>
.
Exhaustive switch cases
function actOnDummyOptions(options: DummyOptions): string {
switch (options) {
case "open":
return "it's open!";
case "closed":
return "it's closed";
case "unknown":
return "i have no idea";
default:
throw new UnreachableCaseError(options);
}
}
ValueOf type
const obj = {
id: "123e4567-e89b-12d3-a456-426655440000",
name: "Test object",
timestamp: 1548768231486,
};
type objKeys = ValueOf<typeof obj>;
ElementOf type
const array = [1, 2, true, false];
type arrayElement = ElementOf<typeof array>;
AsyncOrSync type
Useful as a return type in interfaces or abstract classes with missing implementation
interface CiProvider {
getSHA(): AsyncOrSync<string>;
getSHA(): Promise<string> | string;
}
class Circle implements CiProvider {
getSHA() {
return "abc";
}
}
class Travis implements CiProvider {
async getSHA() {
return "def";
}
}
AsyncOrSyncType<AsyncOrSync<number>>
Awaited type
Unwrap promised type:
Awaited<Promise<number>>
Newable
keywords: constructor, class
Type useful when working with classes (not their instances).
class TestCls {
constructor(arg1: string) {}
}
const t1: Newable<any> = TestCls;
Assertions
keywords: invariant
Simple runtime assertion that narrows involved types using
assertion functions.
Note: This function is not purely type level and leaves minimal runtime trace in generated code.
const something: string | undefined = "abc" as any;
assert(something, "Something has to be defined!");
const anything = "abc" as any;
assert(anything instanceof String, "anything has to be a string!");
Exact
keywords: same, equals, equality
Exact<TYPE, SHAPE>
Checks if TYPE
is exactly the same as SHAPE
, if yes than TYPE
is returned otherwise never
.
type ABC = { a: number; b: number; c: number }
type BC = { b: number; c: number }
type C = { c: number }
Exact<ABC, C>
Exact<C, C>
isExact
isExact<SHAPE>()(value)
is a runtime function that returns (on the type level) value if value is exactly of type
SHAPE
or never
otherwise.
type ABC = { a: number; b: number; c: number };
type BC = { b: number; c: number };
type C = { c: number };
let abc: ABC = { a: 1, b: 2, c: 3 };
let bc: BC = { b: 2, c: 3 };
const isBC = isExact<BC>();
isBC(abc);
isBC(bc);
isExact<BC>()(abc);
XOR
Gets the XOR (Exclusive-OR) type which could make 2 types exclude each other.
type A = { a: string };
type B = { a: number; b: boolean };
type C = { c: number };
let A_XOR_B: XOR<A, B>;
let A_XOR_C: XOR<A, C>;
A_XOR_B = { a: 0 };
A_XOR_B = { b: true };
A_XOR_B = { a: "", b: true };
A_XOR_C = { a: "", c: 0 };
A_XOR_B = { a: 0, b: true };
A_XOR_B = { a: "" };
A_XOR_C = { c: 0 };
Functional type essentials
Head
& Tail
: useful for functional programming, or as building blocks for more complex functional types.
function tail<T extends any[]>(array: T): Tail<T> {
return array.slice(1) as Tail<T>;
}
type FirstParameter<FnT extends (...args: any) => any> = FnT extends (...args: infer ArgsT) => any
? Head<ArgsT>
: never;
TypeScript dependency table
ts-essentials | typescript / type of dependency |
---|
^8.0.0 | ^4.1.0 / peer |
^5.0.0 | ^3.7.0 / peer |
^3.0.1 | ^3.5.0 / peer |
^1.0.1 | ^3.2.2 / dev |
^1.0.0 | ^3.0.3 / dev |
Contributors
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification.
Contributions of any kind welcome! Read more