uni-types 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -171
- package/dist/index.d.cts +2567 -162
- package/dist/index.d.mts +2567 -162
- package/package.json +31 -1
package/README.md
CHANGED
|
@@ -1,46 +1,82 @@
|
|
|
1
|
-
<div
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
3
|
# uni-types
|
|
4
4
|
|
|
5
|
-
Universal TypeScript
|
|
5
|
+
**Universal TypeScript Type Utilities**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A comprehensive collection of type helpers for TypeScript development
|
|
8
8
|
|
|
9
9
|
[![NPM version][npm-image]][npm-url]
|
|
10
|
-
![
|
|
11
|
-
|
|
12
|
-
[![
|
|
10
|
+
[![NPM downloads][download-image]][download-url]
|
|
11
|
+
![TypeScript][typescript-url]
|
|
12
|
+
[![Codecov][codecov-image]][codecov-url]
|
|
13
13
|
[![License][license-image]][license-url]
|
|
14
14
|
|
|
15
|
+
[**Documentation**](https://saqqdy.github.io/uni-types/) · [**中文文档**](./README_CN.md)
|
|
16
|
+
|
|
15
17
|
</div>
|
|
16
18
|
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- 🎯 **100+ Type Utilities** - Comprehensive type helpers for every use case
|
|
22
|
+
- 🔒 **Type Safe** - Full TypeScript support with strict type checking
|
|
23
|
+
- 📦 **Zero Dependencies** - Lightweight and tree-shakeable
|
|
24
|
+
- 🚀 **TypeScript 5.x** - Built with the latest TypeScript features
|
|
25
|
+
- 🌍 **Bilingual Docs** - Documentation in English and Chinese
|
|
26
|
+
|
|
17
27
|
## Installation
|
|
18
28
|
|
|
19
29
|
```bash
|
|
20
|
-
#
|
|
21
|
-
|
|
30
|
+
# pnpm
|
|
31
|
+
pnpm add uni-types
|
|
22
32
|
|
|
23
|
-
#
|
|
24
|
-
|
|
33
|
+
# yarn
|
|
34
|
+
yarn add uni-types
|
|
25
35
|
|
|
26
|
-
#
|
|
27
|
-
|
|
36
|
+
# npm
|
|
37
|
+
npm install uni-types
|
|
28
38
|
```
|
|
29
39
|
|
|
30
|
-
##
|
|
40
|
+
## Quick Start
|
|
31
41
|
|
|
32
42
|
```typescript
|
|
33
|
-
import type {
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
import type {
|
|
44
|
+
PickRequired,
|
|
45
|
+
DeepPartial,
|
|
46
|
+
IsArray,
|
|
47
|
+
Brand,
|
|
48
|
+
If,
|
|
49
|
+
Paths
|
|
50
|
+
} from 'uni-types'
|
|
51
|
+
|
|
52
|
+
// Core: Make specific properties required
|
|
36
53
|
interface User {
|
|
37
54
|
name?: string
|
|
38
55
|
age?: number
|
|
39
56
|
email: string
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
type
|
|
43
|
-
// { name: string; age
|
|
59
|
+
type RequiredUser = PickRequired<User, 'name' | 'age'>
|
|
60
|
+
// { name: string; age: number; email: string }
|
|
61
|
+
|
|
62
|
+
// Deep: Make all nested properties optional
|
|
63
|
+
interface Config {
|
|
64
|
+
database: {
|
|
65
|
+
host: string
|
|
66
|
+
port: number
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type PartialConfig = DeepPartial<Config>
|
|
71
|
+
// { database?: { host?: string; port?: number } }
|
|
72
|
+
|
|
73
|
+
// Brand: Create nominal types
|
|
74
|
+
type UserId = Brand<string, 'UserId'>
|
|
75
|
+
type OrderId = Brand<string, 'OrderId'>
|
|
76
|
+
// UserId and OrderId are not interchangeable!
|
|
77
|
+
|
|
78
|
+
// Conditional: Type-level logic
|
|
79
|
+
type Result = If<true, 'success', 'error'> // 'success'
|
|
44
80
|
```
|
|
45
81
|
|
|
46
82
|
## API Reference
|
|
@@ -54,19 +90,6 @@ type RequiredNameUser = PickRequired<User, 'name'>
|
|
|
54
90
|
| `PickPartial<T, K>` | Make specified properties optional |
|
|
55
91
|
| `OmitPartial<T, K>` | Make properties except specified ones optional |
|
|
56
92
|
|
|
57
|
-
```typescript
|
|
58
|
-
interface User {
|
|
59
|
-
name?: string
|
|
60
|
-
age?: number
|
|
61
|
-
email: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
PickRequired<User, 'name'> // { name: string; age?: number; email: string }
|
|
65
|
-
OmitRequired<User, 'name'> // { name?: string; age: number; email: string }
|
|
66
|
-
PickPartial<User, 'email'> // { name?: string; age?: number; email?: string }
|
|
67
|
-
OmitPartial<User, 'email'> // { name: string; age: number; email?: string }
|
|
68
|
-
```
|
|
69
|
-
|
|
70
93
|
### Tuple Operations
|
|
71
94
|
|
|
72
95
|
| Type | Description |
|
|
@@ -80,17 +103,6 @@ OmitPartial<User, 'email'> // { name: string; age: number; email?: string }
|
|
|
80
103
|
| `TupleLength<T>` | Get tuple length |
|
|
81
104
|
| `IsEmptyTuple<T>` | Check if tuple is empty |
|
|
82
105
|
|
|
83
|
-
```typescript
|
|
84
|
-
Head<[1, 2, 3]> // 1
|
|
85
|
-
Last<[1, 2, 3]> // 3
|
|
86
|
-
Tail<[1, 2, 3]> // [2, 3]
|
|
87
|
-
Init<[1, 2, 3]> // [1, 2]
|
|
88
|
-
Reverse<[1, 2, 3]> // [3, 2, 1]
|
|
89
|
-
Flatten<[1, [2, [3]]]> // [1, 2, 3]
|
|
90
|
-
TupleLength<[1, 2, 3]> // 3
|
|
91
|
-
IsEmptyTuple<[]> // true
|
|
92
|
-
```
|
|
93
|
-
|
|
94
106
|
### Deep Operations
|
|
95
107
|
|
|
96
108
|
| Type | Description |
|
|
@@ -99,17 +111,8 @@ IsEmptyTuple<[]> // true
|
|
|
99
111
|
| `DeepRequired<T>` | Make all nested properties required |
|
|
100
112
|
| `DeepReadonly<T>` | Make all nested properties readonly |
|
|
101
113
|
| `DeepMutable<T>` | Make all nested properties mutable |
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
interface Nested {
|
|
105
|
-
a: { b: { c: string } }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
DeepPartial<Nested> // { a?: { b?: { c?: string } } }
|
|
109
|
-
DeepRequired<{ a?: { b?: string } }> // { a: { b: string } }
|
|
110
|
-
DeepReadonly<Nested> // { readonly a: { readonly b: { readonly c: string } } }
|
|
111
|
-
DeepMutable<{ readonly a: { readonly b: string } }> // { a: { b: string } }
|
|
112
|
-
```
|
|
114
|
+
| `DeepOmit<T, P>` | Omit properties by path |
|
|
115
|
+
| `DeepPick<T, P>` | Pick properties by path |
|
|
113
116
|
|
|
114
117
|
### Type Guards
|
|
115
118
|
|
|
@@ -121,80 +124,62 @@ DeepMutable<{ readonly a: { readonly b: string } }> // { a: { b: string } }
|
|
|
121
124
|
| `IsAny<T>` | Check if type is `any` |
|
|
122
125
|
| `IsNever<T>` | Check if type is `never` |
|
|
123
126
|
| `IsUnknown<T>` | Check if type is `unknown` |
|
|
127
|
+
| `IsFunction<T>` | Check if type is a function |
|
|
128
|
+
| `IsAsyncFunction<T>` | Check if type is an async function |
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
IsArray<string[]> // true
|
|
127
|
-
IsArray<string> // false
|
|
128
|
-
IsTuple<[string, number]> // true
|
|
129
|
-
IsTuple<string[]> // false
|
|
130
|
-
IsEqual<string, string> // true
|
|
131
|
-
IsEqual<string, number> // false
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Type Inference
|
|
130
|
+
### Conditional Types *(v1.1.0)*
|
|
135
131
|
|
|
136
132
|
| Type | Description |
|
|
137
133
|
|------|-------------|
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `FirstParameter<T>` | Get first parameter type of function |
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
Awaited<Promise<string>> // string
|
|
147
|
-
Awaited<Promise<Promise<number>>> // number
|
|
148
|
-
ArrayElement<string[]> // string
|
|
149
|
-
ValueOf<{ a: string; b: number }> // string | number
|
|
150
|
-
FunctionKeys<{ name: string; onClick: () => void }> // 'onClick'
|
|
151
|
-
NonFunctionKeys<{ name: string; onClick: () => void }> // 'name'
|
|
152
|
-
```
|
|
134
|
+
| `If<C, T, F>` | Type-level if-then-else |
|
|
135
|
+
| `Not<B>` | Logical NOT for boolean types |
|
|
136
|
+
| `And<A, B>` | Logical AND for boolean types |
|
|
137
|
+
| `Or<A, B>` | Logical OR for boolean types |
|
|
138
|
+
| `Assert<T, U>` | Type constraint assertion |
|
|
153
139
|
|
|
154
|
-
###
|
|
140
|
+
### Brand Types *(v1.1.0)*
|
|
155
141
|
|
|
156
142
|
| Type | Description |
|
|
157
143
|
|------|-------------|
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `Exclusive<T, K>` | Create mutually exclusive properties |
|
|
161
|
-
| `NoNullish<T>` | Remove null/undefined from all properties |
|
|
162
|
-
| `Nullable<T>` | Add `null` to type |
|
|
163
|
-
| `Optional<T>` | Add `undefined` to type |
|
|
164
|
-
| `Maybe<T>` | Add `null` and `undefined` to type |
|
|
165
|
-
| `LoosePartial<T>` | Make all properties optional |
|
|
144
|
+
| `Brand<T, B>` | Create a branded type for nominal typing |
|
|
145
|
+
| `Unbrand<T>` | Extract underlying type from branded type |
|
|
166
146
|
|
|
167
|
-
|
|
168
|
-
Merge<{ a: string; b: number }, { b: boolean; c: string }>
|
|
169
|
-
// { a: string; b: boolean; c: string }
|
|
147
|
+
### Function Utilities *(v1.1.0)*
|
|
170
148
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
149
|
+
| Type | Description |
|
|
150
|
+
|------|-------------|
|
|
151
|
+
| `Parameters<T>` | Get function parameters as tuple |
|
|
152
|
+
| `ReturnType<T>` | Get function return type |
|
|
153
|
+
| `NthParameter<T, N>` | Get Nth parameter type |
|
|
154
|
+
| `AsyncReturnType<T>` | Extract async function return type |
|
|
155
|
+
| `ThisParameterType<T>` | Get `this` parameter type |
|
|
156
|
+
| `OmitThisParameter<T>` | Omit `this` parameter from function |
|
|
176
157
|
|
|
177
|
-
###
|
|
158
|
+
### Template Literal Utilities *(v1.1.0)*
|
|
178
159
|
|
|
179
160
|
| Type | Description |
|
|
180
161
|
|------|-------------|
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
162
|
+
| `ReplaceAll<S, From, To>` | Replace all occurrences |
|
|
163
|
+
| `Replace<S, From, To>` | Replace first occurrence |
|
|
164
|
+
| `Trim<S>` | Trim whitespace |
|
|
165
|
+
| `StringToArray<S>` | Convert string to array |
|
|
166
|
+
| `CapitalizeAll<S>` | Capitalize all words |
|
|
167
|
+
| `StartsWith<S, P>` | Check if string starts with prefix |
|
|
168
|
+
| `EndsWith<S, P>` | Check if string ends with suffix |
|
|
169
|
+
| `StringLength<S>` | Get string length |
|
|
185
170
|
|
|
186
|
-
|
|
187
|
-
interface User {
|
|
188
|
-
name: string
|
|
189
|
-
age?: number
|
|
190
|
-
readonly id: number
|
|
191
|
-
}
|
|
171
|
+
### Numeric Utilities *(v1.1.0)*
|
|
192
172
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
173
|
+
| Type | Description |
|
|
174
|
+
|------|-------------|
|
|
175
|
+
| `Inc<N>` | Increment number |
|
|
176
|
+
| `Dec<N>` | Decrement number |
|
|
177
|
+
| `Add<A, B>` | Add two numbers |
|
|
178
|
+
| `Subtract<A, B>` | Subtract two numbers |
|
|
179
|
+
| `GreaterThan<A, B>` | Check if A > B |
|
|
180
|
+
| `LessThan<A, B>` | Check if A < B |
|
|
181
|
+
| `Max<A, B>` | Maximum of two numbers |
|
|
182
|
+
| `Min<A, B>` | Minimum of two numbers |
|
|
198
183
|
|
|
199
184
|
### Path Types
|
|
200
185
|
|
|
@@ -202,69 +187,98 @@ ReadonlyKeys<User> // 'id'
|
|
|
202
187
|
|------|-------------|
|
|
203
188
|
| `Paths<T>` | Get all nested property paths |
|
|
204
189
|
| `PathValue<T, P>` | Get value type at path |
|
|
205
|
-
| `
|
|
190
|
+
| `ValidPath<T, P>` | Check if path is valid |
|
|
191
|
+
| `ArrayPaths<T>` | Get paths including array indices |
|
|
192
|
+
| `LeafPaths<T>` | Get paths to primitive values |
|
|
206
193
|
|
|
207
|
-
|
|
208
|
-
interface Obj {
|
|
209
|
-
a: { b: { c: string } }
|
|
210
|
-
}
|
|
194
|
+
### Key Utilities *(v1.1.0)*
|
|
211
195
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
196
|
+
| Type | Description |
|
|
197
|
+
|------|-------------|
|
|
198
|
+
| `Keys<T>` | Get all keys |
|
|
199
|
+
| `RenameKeys<T, M>` | Rename keys based on mapping |
|
|
200
|
+
| `PrefixKeys<T, P>` | Add prefix to all keys |
|
|
201
|
+
| `SuffixKeys<T, S>` | Add suffix to all keys |
|
|
202
|
+
| `KeysByValueType<T, V>` | Get keys by value type |
|
|
216
203
|
|
|
217
|
-
###
|
|
204
|
+
### Record Utilities *(v1.1.0)*
|
|
218
205
|
|
|
219
206
|
| Type | Description |
|
|
220
207
|
|------|-------------|
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
208
|
+
| `DeepNullable<T>` | Make all properties nullable |
|
|
209
|
+
| `DeepOptional<T>` | Make all properties optional |
|
|
210
|
+
| `Immutable<T>` | Make all properties readonly |
|
|
211
|
+
| `Mutable<T>` | Make all properties mutable |
|
|
212
|
+
| `DeepNonNullable<T>` | Remove null/undefined from all properties |
|
|
213
|
+
| `Exact<T, Shape>` | Ensure exact shape match |
|
|
225
214
|
|
|
226
|
-
|
|
227
|
-
Literal // string | number | boolean | undefined | null | void | bigint
|
|
228
|
-
LiteralString<'hello'> // 'hello'
|
|
229
|
-
LiteralNumber<42> // 42
|
|
230
|
-
LiteralBoolean<true> // true
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### String Case Conversion
|
|
215
|
+
### Schema Validation *(v1.2.0)*
|
|
234
216
|
|
|
235
217
|
| Type | Description |
|
|
236
218
|
|------|-------------|
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
219
|
+
| `RuntimeGuard<T>` | Define type guard function for runtime checking |
|
|
220
|
+
| `GuardedType<G>` | Extract type from type guard function |
|
|
221
|
+
| `HasRuntimeCheck<T>` | Check if type has runtime check available |
|
|
222
|
+
| `ZodOutput<T>` | Extract output type from Zod schema |
|
|
223
|
+
| `ZodInput<T>` | Extract input type from Zod schema |
|
|
224
|
+
| `ZodShape<T>` | Extract shape from ZodObject schema |
|
|
225
|
+
| `ZodRequiredKeys<T>` | Get required keys from Zod schema |
|
|
226
|
+
| `ZodOptionalKeys<T>` | Get optional keys from Zod schema |
|
|
227
|
+
| `YupOutput<T>` | Extract output type from Yup schema |
|
|
228
|
+
| `YupInput<T>` | Extract input type from Yup schema |
|
|
229
|
+
|
|
230
|
+
### Ecosystem Integration *(v1.2.0)*
|
|
249
231
|
|
|
250
|
-
|
|
232
|
+
| Type | Description |
|
|
233
|
+
|------|-------------|
|
|
234
|
+
| `ComponentProps<T>` | Extract props from React component |
|
|
235
|
+
| `PropsWithChildren<P>` | Add children to props type |
|
|
236
|
+
| `RequiredProps<P>` | Get required prop keys |
|
|
237
|
+
| `OptionalProps<P>` | Get optional prop keys |
|
|
238
|
+
| `VuePropType<T>` | Vue prop type definition |
|
|
239
|
+
| `VueEmitType<T>` | Vue emit function type |
|
|
240
|
+
| `PrismaCreateInput<T>` | Create input type for Prisma models |
|
|
241
|
+
| `PrismaUpdateInput<T>` | Update input type for Prisma models |
|
|
242
|
+
| `PrismaWhereInput<T>` | Where input type for Prisma queries |
|
|
243
|
+
| `TRPCProcedureInput<T>` | Extract input from tRPC procedure |
|
|
244
|
+
| `TRPCProcedureOutput<T>` | Extract output from tRPC procedure |
|
|
245
|
+
|
|
246
|
+
### Performance Optimization *(v1.2.0)*
|
|
251
247
|
|
|
252
248
|
| Type | Description |
|
|
253
249
|
|------|-------------|
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
250
|
+
| `Simplify<T>` | Flatten intersection types |
|
|
251
|
+
| `DeepSimplify<T>` | Recursively simplify nested types |
|
|
252
|
+
| `Compact<T>` | Remove never and undefined properties |
|
|
253
|
+
| `StripNever<T>` | Remove never properties |
|
|
254
|
+
| `StripUndefined<T>` | Remove undefined properties |
|
|
255
|
+
| `MergeAll<T>` | Merge multiple object types |
|
|
256
|
+
| `Lazy<T>` | Defer type evaluation |
|
|
257
|
+
| `Cached<T>` | Cache type computation |
|
|
258
|
+
| `Memoized<T>` | Memoize type computation |
|
|
259
|
+
|
|
260
|
+
## Examples
|
|
261
261
|
|
|
262
262
|
```typescript
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
import type {
|
|
264
|
+
SnakeCase,
|
|
265
|
+
CamelCaseKeys,
|
|
266
|
+
UnionToIntersection,
|
|
267
|
+
AtLeastOne
|
|
268
|
+
} from 'uni-types'
|
|
269
|
+
|
|
270
|
+
// String case conversion
|
|
271
|
+
SnakeCase<'XMLParser'> // 'xml_parser'
|
|
272
|
+
CamelCaseKeys<{ user_name: string, user_age: number }>
|
|
273
|
+
// { userName: string, userAge: number }
|
|
274
|
+
|
|
275
|
+
// Union to intersection
|
|
276
|
+
UnionToIntersection<{ a: string } | { b: number }>
|
|
277
|
+
// { a: string } & { b: number }
|
|
278
|
+
|
|
279
|
+
// Require at least one property
|
|
280
|
+
type Options = AtLeastOne<{ a?: string; b?: number; c?: boolean }>
|
|
281
|
+
// Must have at least one of a, b, or c
|
|
268
282
|
```
|
|
269
283
|
|
|
270
284
|
## Development
|
|
@@ -273,32 +287,33 @@ UnionToIntersection<{ a: string } | { b: number }> // { a: string } & { b: numb
|
|
|
273
287
|
# Install dependencies
|
|
274
288
|
pnpm install
|
|
275
289
|
|
|
276
|
-
#
|
|
277
|
-
pnpm build
|
|
278
|
-
|
|
279
|
-
# Test
|
|
290
|
+
# Run tests
|
|
280
291
|
pnpm test
|
|
281
292
|
|
|
282
|
-
#
|
|
283
|
-
pnpm
|
|
293
|
+
# Build
|
|
294
|
+
pnpm build
|
|
284
295
|
|
|
285
296
|
# Type check
|
|
286
297
|
pnpm typecheck
|
|
287
298
|
|
|
288
|
-
#
|
|
289
|
-
pnpm
|
|
299
|
+
# Start docs dev server
|
|
300
|
+
pnpm docs:dev
|
|
290
301
|
```
|
|
291
302
|
|
|
303
|
+
## Contributing
|
|
304
|
+
|
|
305
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
|
|
306
|
+
|
|
292
307
|
## License
|
|
293
308
|
|
|
294
|
-
[MIT](LICENSE)
|
|
309
|
+
[MIT](LICENSE) © [saqqdy](https://github.com/saqqdy)
|
|
295
310
|
|
|
296
311
|
[npm-image]: https://img.shields.io/npm/v/uni-types.svg?style=flat-square
|
|
297
312
|
[npm-url]: https://npmjs.org/package/uni-types
|
|
298
|
-
[typescript-url]: https://
|
|
313
|
+
[typescript-url]: https://img.shields.io/badge/TypeScript-5.x-3178c6?style=flat-square&logo=typescript&logoColor=white
|
|
299
314
|
[codecov-image]: https://img.shields.io/codecov/c/github/saqqdy/uni-types.svg?style=flat-square
|
|
300
|
-
[codecov-url]: https://codecov.io/github/saqqdy/uni-types
|
|
315
|
+
[codecov-url]: https://codecov.io/github/saqqdy/uni-types
|
|
301
316
|
[download-image]: https://img.shields.io/npm/dm/uni-types.svg?style=flat-square
|
|
302
317
|
[download-url]: https://npmjs.org/package/uni-types
|
|
303
|
-
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg
|
|
304
|
-
[license-url]: LICENSE
|
|
318
|
+
[license-image]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square
|
|
319
|
+
[license-url]: LICENSE
|