597 lines
21 KiB
Markdown
597 lines
21 KiB
Markdown
<p align="center">
|
|
<img src="https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/google/146/toolbox_1f9f0.png" width="120" alt="ts-essentials">
|
|
<h3 align="center">ts-essentials</h3>
|
|
<p align="center">All essential TypeScript types in one place 🤙</p>
|
|
<p align="center">
|
|
<img alt="Downloads" src="https://img.shields.io/npm/dm/ts-essentials.svg">
|
|
<img alt="Build status" src="https://circleci.com/gh/krzkaczor/ts-essentials.svg?style=svg">
|
|
<a href="/package.json"><img alt="Software License" src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square"></a>
|
|
<img src="https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square" alt="All Contributors">
|
|
<a href="https://codechecks.io"><img src="https://raw.githubusercontent.com/codechecks/docs/master/images/badges/badge-default.svg?sanitize=true" alt="codechecks.io"></a>
|
|
</p>
|
|
</p>
|
|
|
|
## Install
|
|
|
|
```sh
|
|
npm install --save-dev ts-essentials
|
|
```
|
|
|
|
👉 We require `typescript>=3.5`. If you're looking for support for older TS versions use `ts-essentials@2` instead.
|
|
|
|
## What's inside?
|
|
|
|
- [Install](#Install)
|
|
- [What's inside?](#Whats-inside)
|
|
- [Basic](#Basic)
|
|
- [Dictionaries](#Dictionaries)
|
|
- [Deep Partial & Deep Required & Deep Readonly & Deep NonNullable](#Deep-Partial--Deep-Required--Deep-Readonly--Deep-NonNullable)
|
|
- [Writable](#Writable)
|
|
- [Buildable](#Buildable)
|
|
- [Omit](#Omit)
|
|
- [StrictOmit](#StrictOmit)
|
|
- [Comparison between `Omit` and `StrictOmit`](#Comparison-between-Omit-and-StrictOmit)
|
|
- [DeepOmit](#DeepOmit)
|
|
- [OmitProperties](#OmitProperties)
|
|
- [PickProperties](#PickProperties)
|
|
- [NonNever](#NonNever)
|
|
- [Merge](#Merge)
|
|
- [MarkRequired](#MarkRequired)
|
|
- [MarkOptional](#MarkOptional)
|
|
- [ReadonlyKeys](#ReadonlyKeys)
|
|
- [WritableKeys](#WritableKeys)
|
|
- [UnionToIntersection](#UnionToIntersection)
|
|
- [Opaque types](#Opaque-types)
|
|
- [Tuple constraint](#Tuple-constraint)
|
|
- [Literal types](#Literal-types)
|
|
- [Exhaustive switch cases](#Exhaustive-switch-cases)
|
|
- [ValueOf type](#ValueOf-type)
|
|
- [AsyncOrSync type](#AsyncOrSync-type)
|
|
- [Contributors](#Contributors)
|
|
|
|
### Basic
|
|
|
|
- `Primitive` type matching all primitive values.
|
|
|
|
### Dictionaries
|
|
|
|
```typescript
|
|
const stringDict: Dictionary<string> = {
|
|
a: "A",
|
|
b: "B",
|
|
};
|
|
|
|
// Specify second type argument to change dictionary keys type
|
|
const dictOfNumbers: Dictionary<string, number> = {
|
|
420: "four twenty",
|
|
1337: "HAX",
|
|
};
|
|
|
|
// You may specify union types as key to cover all possible cases. It acts the same as Record from TS's standard library
|
|
export type DummyOptions = "open" | "closed" | "unknown";
|
|
const dictFromUnionType: Dictionary<number, DummyOptions> = {
|
|
closed: 1,
|
|
open: 2,
|
|
unknown: 3,
|
|
};
|
|
|
|
// and get dictionary values
|
|
type stringDictValues = DictionaryValues<typeof stringDict>;
|
|
// Result: string
|
|
```
|
|
|
|
### Deep Partial & Deep Required & Deep Readonly & Deep NonNullable
|
|
|
|
```typescript
|
|
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}], // Error: Type 'null' is not assignable to type 'number'
|
|
}
|
|
}
|
|
```
|
|
|
|
### Writable
|
|
|
|
Make all attributes of object writable.
|
|
|
|
```typescript
|
|
type Foo = {
|
|
readonly a: number;
|
|
readonly b: string;
|
|
};
|
|
|
|
const foo: Foo = ({ a: 1, b: "b" }(foo as Writable<typeof foo>).a = 42);
|
|
```
|
|
|
|
```typescript
|
|
type Foo = {
|
|
readonly foo: string;
|
|
bar: {
|
|
readonly x: number;
|
|
};
|
|
}[];
|
|
|
|
const test: DeepWritable<Foo> = [
|
|
{
|
|
foo: "a",
|
|
bar: {
|
|
x: 5,
|
|
},
|
|
},
|
|
];
|
|
|
|
// we can freely write to this object
|
|
test[0].foo = "b";
|
|
test[0].bar.x = 2;
|
|
```
|
|
|
|
### Buildable
|
|
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
type ComplexObject = {
|
|
simple: number;
|
|
nested: {
|
|
a: string;
|
|
array: [{ bar: number }];
|
|
};
|
|
};
|
|
|
|
type SimplifiedComplexObject = StrictOmit<ComplexObject, "nested">;
|
|
|
|
// Result:
|
|
// {
|
|
// simple: number
|
|
// }
|
|
|
|
// if you want to Omit multiple properties just use union type:
|
|
type SimplifiedComplexObject = StrictOmit<ComplexObject, "nested" | "simple">;
|
|
|
|
// Result:
|
|
// { } (empty type)
|
|
```
|
|
|
|
#### Comparison between `Omit` and `StrictOmit`
|
|
|
|
Following the code above, we can compare the behavior of `Omit` and `StrictOmit`.
|
|
|
|
```typescript
|
|
type SimplifiedComplexObjectWithStrictOmit = StrictOmit<ComplexObject, "nested" | "simple" | "nonexistent">;
|
|
|
|
// Result: error
|
|
// Type '"simple" | "nested" | "nonexistent"' does not satisfy the constraint '"simple" | "nested"'.
|
|
// Type '"nonexistent"' is not assignable to type '"simple" | "nested"'.
|
|
|
|
type SimplifiedComplexObjectWithOmit = Omit<ComplexObject, "nested" | "simple" | "nonexistent">;
|
|
|
|
// Result: no error
|
|
```
|
|
|
|
As is shown in the example, `StrictOmit` ensures that no extra key is specified in the filter.
|
|
|
|
### DeepOmit
|
|
|
|
Recursively omit deep properties according to key names.
|
|
|
|
Here is the `Teacher` interface.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
type TeacherSimple = DeepOmit<Teacher, {
|
|
gender: never,
|
|
students: {
|
|
score: never,
|
|
}
|
|
}>
|
|
|
|
// The result will be:
|
|
// {
|
|
// name: string,
|
|
// students: {name: string}[]
|
|
// }
|
|
```
|
|
|
|
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
|
|
|
|
Removes all properties extending type `P` in type `T`.
|
|
|
|
```typescript
|
|
interface Example {
|
|
log(): void;
|
|
version: string;
|
|
}
|
|
|
|
type ExampleWithoutMethods = OmitProperties<Example, Function>;
|
|
|
|
// Result:
|
|
// {
|
|
// version: string;
|
|
// }
|
|
|
|
// if you want to Omit multiple properties just use union type like
|
|
|
|
type ExampleWithoutMethods = OmitProperties<Example, Function | string>;
|
|
// Result:
|
|
// { } (empty type)
|
|
|
|
```
|
|
|
|
### PickProperties
|
|
|
|
Pick only properties extending type `P` in type `T`.
|
|
|
|
```typescript
|
|
interface Example {
|
|
log(): void;
|
|
version: string;
|
|
versionNumber: number;
|
|
}
|
|
|
|
type ExampleOnlyMethods = PickProperties<Example, Function>;
|
|
|
|
// Result:
|
|
// {
|
|
// log(): void;
|
|
// }
|
|
|
|
// if you want to pick multiple properties just use union type like
|
|
|
|
type ExampleOnlyMethodsAndString = PickProperties<Example, Function | string>;
|
|
// Result:
|
|
// {
|
|
// log(): void;
|
|
// version: string;
|
|
// }
|
|
|
|
```
|
|
|
|
### NonNever
|
|
|
|
Useful for purifying object types. It improves intellisense but also allows for extracting keys satisfying a conditional
|
|
type.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
/* return never if the object doesn't have any number value*/
|
|
type NumberDictionary<T> = NonEmptyObject<PickProperties<T, number>>;
|
|
|
|
// return { a: number }
|
|
type SomeObject = NumberDictionary<{ a: number, b: string }>;
|
|
|
|
// return never
|
|
type EmptyObject = NumberDictionary<{}>;
|
|
```
|
|
|
|
### Merge
|
|
|
|
```typescript
|
|
type Foo = {
|
|
a: number;
|
|
b: string;
|
|
};
|
|
|
|
type Bar = {
|
|
b: number;
|
|
};
|
|
|
|
const xyz: Merge<Foo, Bar> = { a: 4, b: 2 };
|
|
// Result:
|
|
// {
|
|
// a: number,
|
|
// b: number,
|
|
// }
|
|
```
|
|
|
|
### 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.
|
|
|
|
```typescript
|
|
class User {
|
|
id: number;
|
|
posts?: Post[];
|
|
photos?: Photo[];
|
|
}
|
|
type UserWithPosts = MarkRequired<User, 'posts'>;
|
|
|
|
// example usage with a TypeORM repository -- `posts` are now required, `photos` are still optional
|
|
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.
|
|
|
|
```typescript
|
|
interface User {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
type UserWithoutPassword = MarkOptional<User, 'password'>;
|
|
|
|
// Result:
|
|
|
|
// {
|
|
// id: number;
|
|
// name: string;
|
|
// email: string;
|
|
// password?: string;
|
|
// }
|
|
|
|
```
|
|
|
|
### ReadonlyKeys
|
|
|
|
Gets keys of an object which are readonly.
|
|
|
|
```typescript
|
|
type T = {
|
|
readonly a: number;
|
|
b: string;
|
|
};
|
|
type Result = ReadonlyKeys<T>
|
|
// Result:
|
|
// "a"
|
|
```
|
|
|
|
### WritableKeys
|
|
|
|
Gets keys of an object which are writable.
|
|
|
|
```typescript
|
|
type T = {
|
|
readonly a: number;
|
|
b: string;
|
|
};
|
|
type Result = WritableKeys<T>
|
|
// Result:
|
|
// "b"
|
|
```
|
|
|
|
### UnionToIntersection
|
|
|
|
Useful for converting mapped types with function values to intersection type (so in this case - overloaded function).
|
|
|
|
```typescript
|
|
type Foo = {
|
|
bar: string;
|
|
xyz: number;
|
|
};
|
|
|
|
type Fn = UnionToIntersection<{ [K in keyof Foo]: (type: K, arg: Foo[K]) => any }[keyof Foo]>;
|
|
```
|
|
|
|
### Opaque types
|
|
|
|
```typescript
|
|
type PositiveNumber = Opaque<number, "positive-number">;
|
|
|
|
function makePositiveNumber(n: number): PositiveNumber {
|
|
if (n <= 0) {
|
|
throw new Error("Value not positive !!!");
|
|
}
|
|
return (n as any) as PositiveNumber; // this ugly cast is required but only when "producing" opaque types
|
|
}
|
|
```
|
|
|
|
### Tuple constraint
|
|
|
|
```typescript
|
|
function foo<T extends Tuple>(tuple: T): T {
|
|
return tuple;
|
|
}
|
|
|
|
const ret = foo(["s", 1]);
|
|
// return type of [string, number]
|
|
```
|
|
|
|
You can also parametrize `Tuple` type with a type argument to constraint it to certain types, i.e.
|
|
`Tuple<string | number>`.
|
|
|
|
### Literal types
|
|
|
|
_For TypeScript >= 3.4_: TypeScript 3.4 shipped
|
|
[`const` assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html) which are very
|
|
similar to our `literal` helper but also make type readonly, you should prefer `as const` construct.
|
|
`literal` is deprecated tn `ts-essentials` 3.x, which requires TypeScript >=3.5.
|
|
|
|
_For TypeScript < 3.4_: this is served as a backport of the [`const` assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html) added since TypeScript 3.4.
|
|
|
|
```typescript
|
|
// prevent type widening https://blog.mariusschulz.com/2017/02/04/typescript-2-1-literal-type-widening
|
|
const t = {
|
|
letter: literal("a"), // type stays "a" not string
|
|
digit: literal(5), // type stays 5 not number
|
|
};
|
|
```
|
|
|
|
### Exhaustive switch cases
|
|
|
|
```typescript
|
|
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:
|
|
// if you would add another option to DummyOptions, you'll get error here!
|
|
throw new UnreachableCaseError(options);
|
|
}
|
|
}
|
|
```
|
|
|
|
### ValueOf type
|
|
|
|
```typescript
|
|
const obj = {
|
|
id: "123e4567-e89b-12d3-a456-426655440000",
|
|
name: "Test object",
|
|
timestamp: 1548768231486,
|
|
};
|
|
|
|
type objKeys = ValueOf<typeof obj>;
|
|
// Result: string | number
|
|
```
|
|
|
|
### AsyncOrSync type
|
|
|
|
Useful as a return type in interfaces or abstract classes with missing implementation
|
|
|
|
```typescript
|
|
interface CiProvider {
|
|
getSHA(): AsyncOrSync<string>;
|
|
// same as
|
|
getSHA(): Promise<string> | string;
|
|
}
|
|
|
|
class Circle implements CiProvider {
|
|
// implementation can use sync version
|
|
getSHA() {
|
|
return "abc";
|
|
}
|
|
}
|
|
|
|
class Travis implements CiProvider {
|
|
// implementation can use async version when needed
|
|
async getSHA() {
|
|
// do async call
|
|
return "def";
|
|
}
|
|
}
|
|
```
|
|
|
|
## Contributors
|
|
|
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
|
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
<!-- prettier-ignore -->
|
|
<table>
|
|
<tr>
|
|
<td align="center"><a href="https://twitter.com/krzkaczor"><img src="https://avatars2.githubusercontent.com/u/1814312?v=4" width="100px;" alt="Chris Kaczor"/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=krzkaczor" title="Code">💻</a> <a href="#business-krzkaczor" title="Business development">💼</a> <a href="#example-krzkaczor" title="Examples">💡</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=krzkaczor" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://scholar.google.com/citations?user=3xZtvpAAAAAJ"><img src="https://avatars3.githubusercontent.com/u/9780746?v=4" width="100px;" alt="Xiao Liang"/><br /><sub><b>Xiao Liang</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=yxliang01" title="Code">💻</a> <a href="#ideas-yxliang01" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=yxliang01" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://github.com/Andarist"><img src="https://avatars2.githubusercontent.com/u/9800850?v=4" width="100px;" alt="Mateusz Burzyński"/><br /><sub><b>Mateusz Burzyński</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=Andarist" title="Code">💻</a> <a href="#ideas-Andarist" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=Andarist" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://github.com/macbem"><img src="https://avatars1.githubusercontent.com/u/12464061?v=4" width="100px;" alt="Maciej Bembenista"/><br /><sub><b>Maciej Bembenista</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=macbem" title="Code">💻</a> <a href="#ideas-macbem" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=macbem" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://github.com/MichaelTontchev"><img src="https://avatars0.githubusercontent.com/u/12261336?v=4" width="100px;" alt="Michael Tontchev"/><br /><sub><b>Michael Tontchev</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=MichaelTontchev" title="Code">💻</a> <a href="#ideas-MichaelTontchev" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=MichaelTontchev" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="http://ThomasdH.blogspot.com"><img src="https://avatars0.githubusercontent.com/u/3889750?v=4" width="100px;" alt="Thomas den Hollander"/><br /><sub><b>Thomas den Hollander</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=ThomasdenH" title="Code">💻</a> <a href="#ideas-ThomasdenH" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=ThomasdenH" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://twitter.com/esamatti"><img src="https://avatars3.githubusercontent.com/u/225712?v=4" width="100px;" alt="Esa-Matti Suuronen"/><br /><sub><b>Esa-Matti Suuronen</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=epeli" title="Code">💻</a> <a href="#ideas-epeli" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=epeli" title="Documentation">📖</a></td>
|
|
</tr>
|
|
<tr>
|
|
<td align="center"><a href="https://github.com/IlyaSemenov"><img src="https://avatars1.githubusercontent.com/u/128121?v=4" width="100px;" alt="Ilya Semenov"/><br /><sub><b>Ilya Semenov</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=IlyaSemenov" title="Code">💻</a> <a href="#ideas-IlyaSemenov" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=IlyaSemenov" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://codechecks.io"><img src="https://avatars2.githubusercontent.com/u/46399828?v=4" width="100px;" alt="Code Checks"/><br /><sub><b>Code Checks</b></sub></a><br /><a href="#review-codechecks" title="Reviewed Pull Requests">👀</a></td>
|
|
<td align="center"><a href="http://www.nomiclabs.io"><img src="https://avatars1.githubusercontent.com/u/176499?v=4" width="100px;" alt="Patricio Palladino"/><br /><sub><b>Patricio Palladino</b></sub></a><br /><a href="#ideas-alcuadrado" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
<td align="center"><a href="http://twitter.com/quezak2"><img src="https://avatars0.githubusercontent.com/u/666206?v=4" width="100px;" alt="Artur Kozak"/><br /><sub><b>Artur Kozak</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=quezak" title="Code">💻</a> <a href="#ideas-quezak" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=quezak" title="Documentation">📖</a> <a href="#review-quezak" title="Reviewed Pull Requests">👀</a></td>
|
|
<td align="center"><a href="https://github.com/lucifer1004"><img src="https://avatars2.githubusercontent.com/u/13583761?v=4" width="100px;" alt="Zihua Wu"/><br /><sub><b>Zihua Wu</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=lucifer1004" title="Code">💻</a> <a href="#ideas-lucifer1004" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=lucifer1004" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="http://kevinpeno.com"><img src="https://avatars1.githubusercontent.com/u/343808?v=4" width="100px;" alt="Kevin Peno"/><br /><sub><b>Kevin Peno</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=kevinpeno" title="Code">💻</a></td>
|
|
<td align="center"><a href="https://github.com/DomParfitt"><img src="https://avatars2.githubusercontent.com/u/11363907?v=4" width="100px;" alt="Dom Parfitt"/><br /><sub><b>Dom Parfitt</b></sub></a><br /><a href="#ideas-DomParfitt" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
</tr>
|
|
<tr>
|
|
<td align="center"><a href="https://github.com/EduardoRFS"><img src="https://avatars0.githubusercontent.com/u/3393115?v=4" width="100px;" alt="EduardoRFS"/><br /><sub><b>EduardoRFS</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=EduardoRFS" title="Code">💻</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=EduardoRFS" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://andydvorak.net/"><img src="https://avatars1.githubusercontent.com/u/409245?v=4" width="100px;" alt="Andrew C. Dvorak"/><br /><sub><b>Andrew C. Dvorak</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=acdvorak" title="Documentation">📖</a></td>
|
|
<td align="center"><a href="https://github.com/a1russell"><img src="https://avatars0.githubusercontent.com/u/241628?v=4" width="100px;" alt="Adam Russell"/><br /><sub><b>Adam Russell</b></sub></a><br /><a href="https://github.com/krzkaczor/ts-essentials/commits?author=a1russell" title="Code">💻</a> <a href="https://github.com/krzkaczor/ts-essentials/commits?author=a1russell" title="Documentation">📖</a></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
|
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! [Read more](./CONTRIBUTING.md)
|