@zym-test-zerog/apiclient 1.0.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 +576 -0
- package/dist/index.d.ts +640 -0
- package/dist/index.js +923 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +910 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# API Client Wrapper
|
|
2
|
+
|
|
3
|
+
A powerful and feature-rich API client wrapper for TypeScript/JavaScript applications with built-in token refresh, request signing, comprehensive error handling, and detailed logging.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Modular API Organization**: Organized by API versions (v1, v2) and functionality
|
|
8
|
+
- **Automatic Token Refresh**: Handles 401 errors and automatically refreshes tokens
|
|
9
|
+
- **Request Signing**: Built-in support for request signature generation
|
|
10
|
+
- **Comprehensive Error Handling**: Global error interceptor with custom error handling support
|
|
11
|
+
- **Detailed Logging**: Request/response logging with sensitive data redaction
|
|
12
|
+
- **Request Cancellation**: Support for cancelling individual or all requests
|
|
13
|
+
- **TypeScript Support**: Full type definitions for better development experience
|
|
14
|
+
- **Dual Module Support**: Works with both CommonJS and ES Module
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install api-client-wrapper
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Basic Usage
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { init, apiV1 } from 'api-client-wrapper';
|
|
28
|
+
|
|
29
|
+
init({
|
|
30
|
+
baseURL: 'https://api.example.com',
|
|
31
|
+
timeout: 10000,
|
|
32
|
+
debug: true
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
try {
|
|
37
|
+
const userInfo = await apiV1.getUserInfo();
|
|
38
|
+
console.log(userInfo);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Login Example
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { init, apiV1 } from 'api-client-wrapper';
|
|
49
|
+
|
|
50
|
+
init();
|
|
51
|
+
|
|
52
|
+
async function login() {
|
|
53
|
+
try {
|
|
54
|
+
const result = await apiV1.login({
|
|
55
|
+
username: 'testuser',
|
|
56
|
+
password: 'password123'
|
|
57
|
+
});
|
|
58
|
+
console.log('Login successful:', result);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Login failed:', error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
### Initialization Options
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface ApiClientConfig {
|
|
71
|
+
baseURL?: string; // Default: 'https://www.xxx.com/api'
|
|
72
|
+
timeout?: number; // Default: 30000 (30s)
|
|
73
|
+
headers?: Record<string, string>;
|
|
74
|
+
debug?: boolean; // Default: false
|
|
75
|
+
logLevel?: 'error' | 'warn' | 'info' | 'debug';
|
|
76
|
+
logCallback?: LogCallback;
|
|
77
|
+
tokenRefreshConfig?: TokenRefreshConfig;
|
|
78
|
+
signConfig?: SignConfig;
|
|
79
|
+
errorHandler?: ErrorHandler;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Token Refresh Configuration
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { init } from 'api-client-wrapper';
|
|
87
|
+
|
|
88
|
+
let accessToken = '';
|
|
89
|
+
let refreshToken = '';
|
|
90
|
+
|
|
91
|
+
init({
|
|
92
|
+
tokenRefreshConfig: {
|
|
93
|
+
refreshTokenUrl: '/auth/refresh',
|
|
94
|
+
getToken: () => accessToken,
|
|
95
|
+
setToken: (token) => { accessToken = token; },
|
|
96
|
+
getRefreshToken: () => refreshToken,
|
|
97
|
+
tokenExpiredCode: 401
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Request Signing Configuration
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { init } from 'api-client-wrapper';
|
|
106
|
+
|
|
107
|
+
init({
|
|
108
|
+
signConfig: {
|
|
109
|
+
secretKey: 'your-secret-key',
|
|
110
|
+
algorithm: 'md5', // 'md5' | 'sha1' | 'sha256'
|
|
111
|
+
signHeaderName: 'X-Sign',
|
|
112
|
+
includeParams: true,
|
|
113
|
+
includeHeaders: false
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Custom Error Handler
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { init } from 'api-client-wrapper';
|
|
122
|
+
|
|
123
|
+
init({
|
|
124
|
+
errorHandler: (error) => {
|
|
125
|
+
console.error('Error:', error.name, error.message);
|
|
126
|
+
|
|
127
|
+
if (error.name === 'NetworkError') {
|
|
128
|
+
// Handle network errors
|
|
129
|
+
} else if (error.name === 'UnauthorizedError') {
|
|
130
|
+
// Redirect to login
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Custom Log Callback
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { init } from 'api-client-wrapper';
|
|
140
|
+
|
|
141
|
+
const logs: any[] = [];
|
|
142
|
+
|
|
143
|
+
init({
|
|
144
|
+
debug: true,
|
|
145
|
+
logLevel: 'debug',
|
|
146
|
+
logCallback: (level, message, data) => {
|
|
147
|
+
logs.push({ level, message, data, timestamp: Date.now() });
|
|
148
|
+
// Send logs to external service
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### Core Methods
|
|
156
|
+
|
|
157
|
+
#### `init(config: ApiClientConfig): void`
|
|
158
|
+
|
|
159
|
+
Initialize the API client with configuration options.
|
|
160
|
+
|
|
161
|
+
#### `updateConfig(config: Partial<ApiClientConfig>): void`
|
|
162
|
+
|
|
163
|
+
Update the configuration after initialization.
|
|
164
|
+
|
|
165
|
+
#### `getApiClient(): ApiClient`
|
|
166
|
+
|
|
167
|
+
Get the underlying ApiClient instance for advanced usage.
|
|
168
|
+
|
|
169
|
+
#### `destroy(): void`
|
|
170
|
+
|
|
171
|
+
Destroy the API client instance and cancel all pending requests.
|
|
172
|
+
|
|
173
|
+
### API v1
|
|
174
|
+
|
|
175
|
+
#### Authentication
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { apiV1 } from 'api-client-wrapper';
|
|
179
|
+
|
|
180
|
+
// Login
|
|
181
|
+
await apiV1.login({
|
|
182
|
+
username: string,
|
|
183
|
+
password: string
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Register
|
|
187
|
+
await apiV1.register({
|
|
188
|
+
username: string,
|
|
189
|
+
password: string,
|
|
190
|
+
email: string
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Logout
|
|
194
|
+
await apiV1.logout();
|
|
195
|
+
|
|
196
|
+
// Get user info
|
|
197
|
+
await apiV1.getUserInfo();
|
|
198
|
+
|
|
199
|
+
// Update user info
|
|
200
|
+
await apiV1.updateUserInfo({
|
|
201
|
+
username?: string,
|
|
202
|
+
email?: string,
|
|
203
|
+
avatar?: string
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### User Management
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// Get user list
|
|
211
|
+
await apiV1.getUserList({
|
|
212
|
+
page: number,
|
|
213
|
+
pageSize: number,
|
|
214
|
+
keyword?: string,
|
|
215
|
+
status?: 'active' | 'inactive' | 'banned'
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Get user by ID
|
|
219
|
+
await apiV1.getUserById(userId: string);
|
|
220
|
+
|
|
221
|
+
// Create user
|
|
222
|
+
await apiV1.createUser({
|
|
223
|
+
username: string,
|
|
224
|
+
email: string,
|
|
225
|
+
password: string,
|
|
226
|
+
role?: string
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Update user
|
|
230
|
+
await apiV1.updateUser(userId: string, {
|
|
231
|
+
username?: string,
|
|
232
|
+
email?: string,
|
|
233
|
+
avatar?: string,
|
|
234
|
+
status?: 'active' | 'inactive' | 'banned'
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Delete user
|
|
238
|
+
await apiV1.deleteUser(userId: string);
|
|
239
|
+
|
|
240
|
+
// Batch delete users
|
|
241
|
+
await apiV1.batchDeleteUsers(userIds: string[]);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Product Management
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// Get product list
|
|
248
|
+
await apiV1.getProductList({
|
|
249
|
+
page: number,
|
|
250
|
+
pageSize: number,
|
|
251
|
+
keyword?: string,
|
|
252
|
+
category?: string,
|
|
253
|
+
status?: 'available' | 'out_of_stock' | 'discontinued',
|
|
254
|
+
minPrice?: number,
|
|
255
|
+
maxPrice?: number
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Get product by ID
|
|
259
|
+
await apiV1.getProductById(productId: string);
|
|
260
|
+
|
|
261
|
+
// Create product
|
|
262
|
+
await apiV1.createProduct({
|
|
263
|
+
name: string,
|
|
264
|
+
description: string,
|
|
265
|
+
price: number,
|
|
266
|
+
stock: number,
|
|
267
|
+
category: string,
|
|
268
|
+
images: string[]
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Update product
|
|
272
|
+
await apiV1.updateProduct(productId: string, {
|
|
273
|
+
name?: string,
|
|
274
|
+
description?: string,
|
|
275
|
+
price?: number,
|
|
276
|
+
stock?: number,
|
|
277
|
+
category?: string,
|
|
278
|
+
images?: string[],
|
|
279
|
+
status?: 'available' | 'out_of_stock' | 'discontinued'
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Delete product
|
|
283
|
+
await apiV1.deleteProduct(productId: string);
|
|
284
|
+
|
|
285
|
+
// Update product stock
|
|
286
|
+
await apiV1.updateProductStock(productId: string, stock: number);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### API v2
|
|
290
|
+
|
|
291
|
+
#### Authentication
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { apiV2 } from 'api-client-wrapper';
|
|
295
|
+
|
|
296
|
+
// Login (V2)
|
|
297
|
+
await apiV2.loginV2({
|
|
298
|
+
account: string,
|
|
299
|
+
password: string,
|
|
300
|
+
captcha?: string,
|
|
301
|
+
deviceId?: string
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Register (V2)
|
|
305
|
+
await apiV2.registerV2({
|
|
306
|
+
account: string,
|
|
307
|
+
password: string,
|
|
308
|
+
confirmPassword: string,
|
|
309
|
+
email: string,
|
|
310
|
+
phone?: string,
|
|
311
|
+
captcha: string
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Logout (V2)
|
|
315
|
+
await apiV2.logoutV2();
|
|
316
|
+
|
|
317
|
+
// Get user info (V2)
|
|
318
|
+
await apiV2.getUserInfoV2();
|
|
319
|
+
|
|
320
|
+
// Update user info (V2)
|
|
321
|
+
await apiV2.updateUserInfoV2({
|
|
322
|
+
nickname?: string,
|
|
323
|
+
avatar?: string,
|
|
324
|
+
email?: string,
|
|
325
|
+
phone?: string,
|
|
326
|
+
gender?: 'male' | 'female' | 'other',
|
|
327
|
+
birthday?: string,
|
|
328
|
+
bio?: string
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Change password
|
|
332
|
+
await apiV2.changePasswordV2({
|
|
333
|
+
oldPassword: string,
|
|
334
|
+
newPassword: string,
|
|
335
|
+
confirmPassword: string
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Upload avatar
|
|
339
|
+
await apiV2.uploadAvatarV2(file: File);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### User Management (V2)
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// Get user list (V2)
|
|
346
|
+
await apiV2.getUserListV2({
|
|
347
|
+
page: number,
|
|
348
|
+
pageSize: number,
|
|
349
|
+
keyword?: string,
|
|
350
|
+
status?: 'active' | 'inactive' | 'banned' | 'pending',
|
|
351
|
+
role?: string,
|
|
352
|
+
sortBy?: 'createdAt' | 'updatedAt' | 'lastLoginAt',
|
|
353
|
+
sortOrder?: 'asc' | 'desc'
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Create user (V2)
|
|
357
|
+
await apiV2.createUserV2({
|
|
358
|
+
account: string,
|
|
359
|
+
password: string,
|
|
360
|
+
nickname: string,
|
|
361
|
+
email: string,
|
|
362
|
+
phone?: string,
|
|
363
|
+
roles: string[]
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Update user (V2)
|
|
367
|
+
await apiV2.updateUserV2(userId: string, {
|
|
368
|
+
nickname?: string,
|
|
369
|
+
avatar?: string,
|
|
370
|
+
email?: string,
|
|
371
|
+
phone?: string,
|
|
372
|
+
gender?: 'male' | 'female' | 'other',
|
|
373
|
+
birthday?: string,
|
|
374
|
+
bio?: string,
|
|
375
|
+
status?: 'active' | 'inactive' | 'banned' | 'pending',
|
|
376
|
+
roles?: string[]
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Assign roles
|
|
380
|
+
await apiV2.assignRolesV2(userId: string, roles: string[]);
|
|
381
|
+
|
|
382
|
+
// Reset password
|
|
383
|
+
await apiV2.resetPasswordV2(userId: string, newPassword: string);
|
|
384
|
+
|
|
385
|
+
// Ban user
|
|
386
|
+
await apiV2.banUserV2(userId: string, reason?: string);
|
|
387
|
+
|
|
388
|
+
// Unban user
|
|
389
|
+
await apiV2.unbanUserV2(userId: string);
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Product Management (V2)
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
// Get product list (V2)
|
|
396
|
+
await apiV2.getProductListV2({
|
|
397
|
+
page: number,
|
|
398
|
+
pageSize: number,
|
|
399
|
+
keyword?: string,
|
|
400
|
+
category?: string,
|
|
401
|
+
subcategory?: string,
|
|
402
|
+
tags?: string[],
|
|
403
|
+
status?: 'draft' | 'published' | 'out_of_stock' | 'discontinued',
|
|
404
|
+
visibility?: 'public' | 'private' | 'hidden',
|
|
405
|
+
featured?: boolean,
|
|
406
|
+
minPrice?: number,
|
|
407
|
+
maxPrice?: number,
|
|
408
|
+
inStock?: boolean,
|
|
409
|
+
sortBy?: 'createdAt' | 'updatedAt' | 'price' | 'name' | 'stock',
|
|
410
|
+
sortOrder?: 'asc' | 'desc'
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Get product by slug
|
|
414
|
+
await apiV2.getProductBySlugV2(slug: string);
|
|
415
|
+
|
|
416
|
+
// Create product (V2)
|
|
417
|
+
await apiV2.createProductV2({
|
|
418
|
+
name: string,
|
|
419
|
+
slug?: string,
|
|
420
|
+
description: string,
|
|
421
|
+
shortDescription?: string,
|
|
422
|
+
price: number,
|
|
423
|
+
compareAtPrice?: number,
|
|
424
|
+
costPrice?: number,
|
|
425
|
+
stock: number,
|
|
426
|
+
lowStockThreshold?: number,
|
|
427
|
+
category: string,
|
|
428
|
+
subcategory?: string,
|
|
429
|
+
tags?: string[],
|
|
430
|
+
images: Array<{
|
|
431
|
+
url: string;
|
|
432
|
+
alt?: string;
|
|
433
|
+
order?: number;
|
|
434
|
+
}>,
|
|
435
|
+
variants?: Array<{
|
|
436
|
+
name: string;
|
|
437
|
+
sku: string;
|
|
438
|
+
price: number;
|
|
439
|
+
stock: number;
|
|
440
|
+
attributes: Record<string, string>;
|
|
441
|
+
}>,
|
|
442
|
+
attributes?: Record<string, string>,
|
|
443
|
+
seoTitle?: string,
|
|
444
|
+
seoDescription?: string,
|
|
445
|
+
seoKeywords?: string[],
|
|
446
|
+
status?: 'draft' | 'published' | 'out_of_stock' | 'discontinued',
|
|
447
|
+
visibility?: 'public' | 'private' | 'hidden',
|
|
448
|
+
featured?: boolean,
|
|
449
|
+
weight?: number,
|
|
450
|
+
dimensions?: {
|
|
451
|
+
length: number;
|
|
452
|
+
width: number;
|
|
453
|
+
height: number;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Publish product
|
|
458
|
+
await apiV2.publishProductV2(productId: string);
|
|
459
|
+
|
|
460
|
+
// Unpublish product
|
|
461
|
+
await apiV2.unpublishProductV2(productId: string);
|
|
462
|
+
|
|
463
|
+
// Upload product images
|
|
464
|
+
await apiV2.uploadProductImagesV2(productId: string, files: File[]);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Advanced Usage
|
|
468
|
+
|
|
469
|
+
### Request Cancellation
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { getApiClient } from 'api-client-wrapper';
|
|
473
|
+
|
|
474
|
+
const client = getApiClient();
|
|
475
|
+
const source = client.createCancelToken('request-id');
|
|
476
|
+
|
|
477
|
+
client.cancelRequest('request-id');
|
|
478
|
+
client.cancelAllRequests();
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Dynamic Configuration Update
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { init, updateConfig } from 'api-client-wrapper';
|
|
485
|
+
|
|
486
|
+
init({
|
|
487
|
+
baseURL: 'https://api.example.com',
|
|
488
|
+
debug: false
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
updateConfig({
|
|
492
|
+
debug: true,
|
|
493
|
+
logLevel: 'info',
|
|
494
|
+
timeout: 15000
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Error Handling
|
|
499
|
+
|
|
500
|
+
The library provides comprehensive error handling with the following error types:
|
|
501
|
+
|
|
502
|
+
- **NetworkError**: Network connectivity issues
|
|
503
|
+
- **ServerError**: Server-side errors (5xx)
|
|
504
|
+
- **UnauthorizedError**: Authentication failures (401)
|
|
505
|
+
- **ForbiddenError**: Access denied (403)
|
|
506
|
+
- **NotFoundError**: Resource not found (404)
|
|
507
|
+
- **ClientError**: Client-side errors (4xx)
|
|
508
|
+
- **RequestSetupError**: Request configuration errors
|
|
509
|
+
|
|
510
|
+
Each error includes:
|
|
511
|
+
- `name`: Error type
|
|
512
|
+
- `message`: Error description
|
|
513
|
+
- `code`: Error code
|
|
514
|
+
- `status`: HTTP status code (if applicable)
|
|
515
|
+
- `data`: Response data (if applicable)
|
|
516
|
+
- `config`: Request configuration
|
|
517
|
+
- `response`: Axios response object
|
|
518
|
+
|
|
519
|
+
## Logging
|
|
520
|
+
|
|
521
|
+
When `debug` is enabled, the library logs detailed request/response information:
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
================================================== login start ==================================================
|
|
525
|
+
url: https://api.example.com/v1/auth/login
|
|
526
|
+
method: POST
|
|
527
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': '***REDACTED***' }
|
|
528
|
+
params: { username: 'test', password: '***REDACTED***' }
|
|
529
|
+
timestamp: 1234567890
|
|
530
|
+
================================================= login end ===================================================
|
|
531
|
+
url: https://api.example.com/v1/auth/login
|
|
532
|
+
method: POST
|
|
533
|
+
status: 200
|
|
534
|
+
statusText: OK
|
|
535
|
+
data: { token: '...', refreshToken: '...' }
|
|
536
|
+
timestamp: 1234567895
|
|
537
|
+
duration: 5ms
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Sensitive information (passwords, tokens, etc.) is automatically redacted.
|
|
541
|
+
|
|
542
|
+
## Development
|
|
543
|
+
|
|
544
|
+
### Build
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
npm run build
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Test
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
npm test
|
|
554
|
+
npm run test:coverage
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Lint
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
npm run lint
|
|
561
|
+
npm run lint:fix
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Type Check
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
npm run typecheck
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## License
|
|
571
|
+
|
|
572
|
+
MIT
|
|
573
|
+
|
|
574
|
+
## Contributing
|
|
575
|
+
|
|
576
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|