koa-ts-core 0.0.31 → 0.0.32
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 +254 -154
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,106 +1,173 @@
|
|
|
1
|
-
#
|
|
1
|
+
# koa-ts-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
基于 **TypeScript + Koa** 的轻量服务端核心库,提供:
|
|
4
|
+
|
|
5
|
+
- 约定式路由(装饰器)
|
|
6
|
+
- 权限与参数校验约定
|
|
7
|
+
- 全局错误处理与统一响应体
|
|
8
|
+
- 环境变量加载
|
|
9
|
+
- 日志(log4js)集成
|
|
10
|
+
- 接口文档生成基础能力(配合 `koa-ts-cli` 使用)
|
|
11
|
+
|
|
12
|
+
> 推荐配合脚手架工具 [koa-ts-cli](https://www.npmjs.com/package/koa-typescript-cli) 使用。
|
|
13
|
+
|
|
14
|
+
---
|
|
4
15
|
|
|
5
16
|
## 安装
|
|
6
17
|
|
|
7
|
-
```
|
|
18
|
+
```bash
|
|
8
19
|
npm i koa-ts-core
|
|
20
|
+
# 或
|
|
21
|
+
yarn add koa-ts-core
|
|
9
22
|
```
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
---
|
|
12
25
|
|
|
13
|
-
|
|
14
|
-
import { initializeKoaApp } from "koa-ts-core";
|
|
26
|
+
## 快速开始
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return Promise.resolve(true);
|
|
20
|
-
},
|
|
21
|
-
// 高优先级中间件注册
|
|
22
|
-
registerHighPriorityMiddleware: (app: Koa) => void,
|
|
23
|
-
// 异常处理
|
|
24
|
-
catchErrorCallback: (error: Error) => void,
|
|
25
|
-
// hook
|
|
26
|
-
registerHook: (
|
|
27
|
-
ctx: Koa.Context,
|
|
28
|
-
type: "request" | "response" | "error"
|
|
29
|
-
) => void;
|
|
30
|
-
// 是否挂载log4到context
|
|
31
|
-
log4: TLog4;
|
|
32
|
-
// 是否挂载运行时日志
|
|
33
|
-
runtimeLog: boolean;
|
|
34
|
-
});
|
|
28
|
+
### 初始化应用
|
|
29
|
+
|
|
30
|
+
在项目入口文件(如 `src/main.ts`)中:
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
```ts
|
|
33
|
+
import Koa from "koa";
|
|
34
|
+
import cors from "koa2-cors";
|
|
35
|
+
import { initializeKoaApp, TLog4 } from "koa-ts-core";
|
|
36
|
+
|
|
37
|
+
async function bootstrap() {
|
|
38
|
+
const [app] = await initializeKoaApp({
|
|
39
|
+
// 鉴权逻辑(配合 @AuthRouter 使用)
|
|
40
|
+
authCheckCallback: async (ctx) => {
|
|
41
|
+
// TODO: 你的权限判断
|
|
42
|
+
return true;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// CORS 中间件
|
|
46
|
+
corsMiddleware: cors(),
|
|
47
|
+
|
|
48
|
+
// 高优先级中间件(在内置中间件之前执行)
|
|
49
|
+
registerHighPriorityMiddleware: (app: Koa) => {
|
|
50
|
+
// app.use(yourMiddleware);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// 全局错误回调
|
|
54
|
+
catchErrorCallback: (error, ctx) => {
|
|
55
|
+
console.error("Global error:", error);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// 请求生命周期 hook:request / response / error
|
|
59
|
+
registerHook: (ctx, type) => {
|
|
60
|
+
// your tracing / metrics logic
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// 日志:true 为默认配置,函数为自定义配置
|
|
64
|
+
log4: true as TLog4,
|
|
65
|
+
|
|
66
|
+
// 是否开启运行时日志
|
|
67
|
+
runtimeLog: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
app.listen(process.env.APP_PORT ?? 3000, () => {
|
|
71
|
+
console.log(
|
|
72
|
+
`Server running at http://localhost:${process.env.APP_PORT ?? 3000}`
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
bootstrap();
|
|
37
78
|
```
|
|
38
79
|
|
|
39
|
-
|
|
80
|
+
---
|
|
40
81
|
|
|
41
|
-
|
|
82
|
+
## 初始化配置说明
|
|
42
83
|
|
|
43
|
-
|
|
84
|
+
`initializeKoaApp(options?: TInitOptions)` 支持以下字段:
|
|
44
85
|
|
|
45
|
-
|
|
86
|
+
- **koaInstance?: Koa**
|
|
87
|
+
自定义 Koa 实例,不传则自动创建。
|
|
46
88
|
|
|
47
|
-
|
|
89
|
+
- **corsMiddleware?: Koa.Middleware**
|
|
90
|
+
CORS 中间件。
|
|
48
91
|
|
|
49
|
-
|
|
92
|
+
- **authCheckCallback?: (ctx: Koa.Context) => boolean | Promise<boolean>**
|
|
93
|
+
鉴权回调,配合 `@AuthRouter` 使用。
|
|
50
94
|
|
|
51
|
-
|
|
95
|
+
- **catchErrorCallback?: (error: Error, ctx?: Koa.Context) => void**
|
|
96
|
+
全局错误回调,用于统一处理未捕获错误。
|
|
52
97
|
|
|
53
|
-
|
|
98
|
+
- **registerHighPriorityMiddleware?: (app: Koa) => void**
|
|
99
|
+
高优先级中间件注册函数,在框架内置中间件之前执行。
|
|
54
100
|
|
|
55
|
-
|
|
101
|
+
- **registerHook?: (ctx: Koa.Context, type: "request" | "response" | "error") => void**
|
|
102
|
+
请求生命周期 hook。
|
|
56
103
|
|
|
57
|
-
|
|
104
|
+
- **log4?: TLog4**
|
|
105
|
+
是否挂载 log4js 及配置方式:
|
|
58
106
|
|
|
59
|
-
|
|
107
|
+
- `false` / 不传:不启用 log4
|
|
108
|
+
- `true`:使用默认 log4js 配置
|
|
109
|
+
- `(instance: Log4js) => Log4js`:自定义配置
|
|
60
110
|
|
|
61
|
-
|
|
111
|
+
- **runtimeLog?: boolean**
|
|
112
|
+
是否挂载运行时日志能力到 `ctx.runtimeLog`。
|
|
62
113
|
|
|
63
|
-
|
|
114
|
+
---
|
|
64
115
|
|
|
65
|
-
|
|
116
|
+
## 中间件链路
|
|
66
117
|
|
|
67
|
-
|
|
118
|
+
内置中间件按以下顺序执行:
|
|
68
119
|
|
|
69
|
-
|
|
120
|
+
1. AsyncLocalStorage 上下文中间件
|
|
121
|
+
2. 全局错误处理
|
|
122
|
+
3. 请求日志 / 响应时间统计
|
|
123
|
+
4. CORS / 安全相关中间件
|
|
124
|
+
5. Body 解析
|
|
125
|
+
6. 鉴权(`authCheckCallback` + `@AuthRouter`)
|
|
126
|
+
7. 约定式路由分发
|
|
70
127
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 约定式路由
|
|
131
|
+
|
|
132
|
+
### 目录约定
|
|
133
|
+
|
|
134
|
+
控制器统一放在 `src/controller` 目录下,例如:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
src
|
|
138
|
+
└── controller
|
|
139
|
+
└── api
|
|
140
|
+
└── v1
|
|
141
|
+
└── user.ts
|
|
76
142
|
```
|
|
77
143
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
144
|
+
### 基础用法:@Router 与 @AuthRouter
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// src/controller/api/v1/user.ts
|
|
81
148
|
import { Context } from "koa";
|
|
149
|
+
import { Router, AuthRouter } from "koa-ts-core";
|
|
82
150
|
|
|
83
151
|
class User {
|
|
84
|
-
|
|
85
152
|
@Router("get", "/index/:userId")
|
|
86
153
|
async userInfo(ctx: Context) {
|
|
87
154
|
ctx.body = {
|
|
88
155
|
success: true,
|
|
156
|
+
userId: ctx.params.userId,
|
|
89
157
|
};
|
|
90
158
|
}
|
|
91
159
|
|
|
92
|
-
//
|
|
160
|
+
// 支持可选路由参数
|
|
93
161
|
@Router("get", "/user_list{/:user_name}")
|
|
94
162
|
async userList(ctx: Context) {
|
|
95
|
-
const userName = ctx.params.user_name;
|
|
96
163
|
ctx.body = {
|
|
97
164
|
success: true,
|
|
98
|
-
userName
|
|
165
|
+
userName: ctx.params.user_name,
|
|
99
166
|
};
|
|
100
167
|
}
|
|
101
168
|
|
|
102
|
-
|
|
103
|
-
|
|
169
|
+
// 需要鉴权的路由
|
|
170
|
+
@AuthRouter("post", "/set_user_info")
|
|
104
171
|
async setUserInfo(ctx: Context) {
|
|
105
172
|
ctx.body = {
|
|
106
173
|
success: true,
|
|
@@ -111,78 +178,77 @@ class User {
|
|
|
111
178
|
export default User;
|
|
112
179
|
```
|
|
113
180
|
|
|
114
|
-
|
|
181
|
+
### REST 风格简写
|
|
115
182
|
|
|
116
|
-
|
|
183
|
+
在不指定 method/path 时,`@Router` 会根据方法名进行推断:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
117
186
|
import Koa from "koa";
|
|
118
187
|
import { Router, successRsp } from "koa-ts-core";
|
|
119
188
|
|
|
120
189
|
class Test {
|
|
121
190
|
@Router()
|
|
122
191
|
async get_(ctx: Koa.Context) {
|
|
123
|
-
successRsp({
|
|
124
|
-
data: "get request",
|
|
125
|
-
});
|
|
192
|
+
successRsp({ data: "get request" });
|
|
126
193
|
}
|
|
127
194
|
|
|
128
195
|
@Router()
|
|
129
196
|
async post(ctx: Koa.Context) {
|
|
130
|
-
successRsp({
|
|
131
|
-
data: "post request",
|
|
132
|
-
});
|
|
197
|
+
successRsp({ data: "post request" });
|
|
133
198
|
}
|
|
134
199
|
|
|
135
200
|
@Router()
|
|
136
201
|
async put(ctx: Koa.Context) {
|
|
137
|
-
successRsp({
|
|
138
|
-
data: "put request",
|
|
139
|
-
});
|
|
202
|
+
successRsp({ data: "put request" });
|
|
140
203
|
}
|
|
141
204
|
|
|
142
205
|
@Router()
|
|
143
206
|
async delete(ctx: Koa.Context) {
|
|
144
|
-
successRsp({
|
|
145
|
-
data: "delete request",
|
|
146
|
-
});
|
|
207
|
+
successRsp({ data: "delete request" });
|
|
147
208
|
}
|
|
148
209
|
|
|
149
210
|
@Router()
|
|
150
211
|
async patch(ctx: Koa.Context) {
|
|
151
|
-
successRsp({
|
|
152
|
-
data: "patch request",
|
|
153
|
-
});
|
|
212
|
+
successRsp({ data: "patch request" });
|
|
154
213
|
}
|
|
155
214
|
|
|
156
215
|
@Router()
|
|
157
216
|
async options(ctx: Koa.Context) {
|
|
158
|
-
successRsp({
|
|
159
|
-
data: "options request",
|
|
160
|
-
});
|
|
217
|
+
successRsp({ data: "options request" });
|
|
161
218
|
}
|
|
162
219
|
}
|
|
163
220
|
|
|
164
221
|
export default Test;
|
|
165
222
|
```
|
|
166
223
|
|
|
167
|
-
|
|
224
|
+
> 实现基于 [`@koa/router`](https://www.npmjs.com/package/@koa/router),路由参数格式参考 [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp)。
|
|
168
225
|
|
|
169
|
-
|
|
226
|
+
---
|
|
170
227
|
|
|
171
|
-
|
|
228
|
+
## 参数与权限校验约定
|
|
172
229
|
|
|
173
|
-
###
|
|
230
|
+
### 目录结构
|
|
174
231
|
|
|
175
|
-
|
|
232
|
+
每个控制器方法对应一个校验器静态方法,目录约定如下:
|
|
176
233
|
|
|
177
|
-
```
|
|
178
|
-
|
|
234
|
+
```text
|
|
235
|
+
src
|
|
236
|
+
├── controller
|
|
237
|
+
│ └── api/v1/user.ts # 控制器
|
|
238
|
+
└── validate
|
|
239
|
+
└── api/v1/user.ts # 参数校验器
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
控制器中有方法 `userInfo` 时,对应的校验器为:
|
|
179
243
|
|
|
180
|
-
|
|
244
|
+
```ts
|
|
245
|
+
// src/validate/api/v1/user.ts
|
|
246
|
+
import { Context } from "koa";
|
|
181
247
|
|
|
182
248
|
class UserValidate {
|
|
183
249
|
static userInfo(ctx: Context) {
|
|
184
250
|
if (!ctx.query.id) {
|
|
185
|
-
throw new Error(
|
|
251
|
+
throw new Error("id is required");
|
|
186
252
|
}
|
|
187
253
|
}
|
|
188
254
|
}
|
|
@@ -190,36 +256,49 @@ class UserValidate {
|
|
|
190
256
|
export default UserValidate;
|
|
191
257
|
```
|
|
192
258
|
|
|
193
|
-
|
|
259
|
+
当路由命中 `userInfo` 时,会自动调用 `UserValidate.userInfo`,抛出的异常会进入统一错误处理。
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 统一响应工具
|
|
194
264
|
|
|
195
|
-
|
|
265
|
+
无需频繁访问 `ctx`,可使用以下工具构造统一格式响应:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
196
268
|
/**
|
|
197
|
-
*
|
|
198
|
-
* @param options - 成功响应的选项,如数据和消息
|
|
269
|
+
* 成功响应(HTTP 状态码 200)
|
|
199
270
|
*/
|
|
200
|
-
|
|
271
|
+
successRsp({ data, message });
|
|
272
|
+
|
|
201
273
|
/**
|
|
202
|
-
*
|
|
203
|
-
* @param options - 包含可能的错误码、消息和数据
|
|
274
|
+
* 业务失败响应(HTTP 状态码仍为 200,但业务 code 非 0)
|
|
204
275
|
*/
|
|
205
|
-
|
|
276
|
+
unSuccessRsp({ code, message, data });
|
|
277
|
+
|
|
206
278
|
/**
|
|
207
|
-
*
|
|
208
|
-
* @param statusCode - 要返回的 HTTP 状态码
|
|
209
|
-
* @param options - 包含可能的错误信息、数据等
|
|
279
|
+
* 异常响应(HTTP 状态码非 200)
|
|
210
280
|
*/
|
|
281
|
+
errorRsp(400, { message: "Bad Request" });
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
类型签名(简化):
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
export declare const successRsp: (options?: Options) => void;
|
|
288
|
+
export declare const unSuccessRsp: (options?: Options) => void;
|
|
211
289
|
export declare const errorRsp: (statusCode: number, options?: Options) => void;
|
|
212
290
|
```
|
|
213
291
|
|
|
214
|
-
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 异常处理
|
|
215
295
|
|
|
216
|
-
|
|
296
|
+
提供基础异常类 `BaseException`,可扩展自定义业务异常:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
217
299
|
import { BaseException } from "koa-ts-core";
|
|
218
|
-
|
|
219
|
-
* 自定义异常
|
|
220
|
-
*/
|
|
300
|
+
|
|
221
301
|
export class CustomException extends BaseException {
|
|
222
|
-
// 自定义异常码
|
|
223
302
|
code = 1000;
|
|
224
303
|
|
|
225
304
|
constructor(message: string) {
|
|
@@ -227,75 +306,61 @@ export class CustomException extends BaseException {
|
|
|
227
306
|
}
|
|
228
307
|
}
|
|
229
308
|
|
|
309
|
+
// 使用
|
|
230
310
|
throw new CustomException("自定义异常");
|
|
231
311
|
```
|
|
232
312
|
|
|
233
|
-
|
|
313
|
+
所有异常会被内置错误处理中间件捕获,并结合 `catchErrorCallback` 输出日志或自定义处理。
|
|
234
314
|
|
|
235
|
-
|
|
315
|
+
---
|
|
236
316
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
317
|
+
## 环境变量加载
|
|
318
|
+
|
|
319
|
+
约定使用 [dotenv](https://www.npmjs.com/package/dotenv) 自动加载 `env` 目录:
|
|
320
|
+
|
|
321
|
+
```text
|
|
322
|
+
env
|
|
323
|
+
├── .common.env # 所有环境公用
|
|
324
|
+
├── .development.env # 开发环境
|
|
325
|
+
├── .production.env # 生产环境
|
|
326
|
+
└── .test.env # 测试环境
|
|
243
327
|
```
|
|
244
328
|
|
|
245
|
-
|
|
329
|
+
根据 `NODE_ENV` 加载对应文件,并合并 `.common.env`。
|
|
246
330
|
|
|
247
|
-
|
|
331
|
+
常用环境变量:
|
|
248
332
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
import Koa from 'koa';
|
|
252
|
-
import { successRsp, Router, AuthRouter } from 'koa-ts-core';
|
|
333
|
+
- `process.env.APP_PORT`:服务监听端口
|
|
334
|
+
- 其他自定义业务配置
|
|
253
335
|
|
|
254
|
-
|
|
255
|
-
@Router('get')
|
|
256
|
-
async get_test(ctx: Koa.Context) {
|
|
257
|
-
successRsp();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
336
|
+
---
|
|
260
337
|
|
|
261
|
-
|
|
338
|
+
## 文档生成(配合 koa-ts-cli)
|
|
262
339
|
|
|
263
|
-
|
|
264
|
-
export default class Test {
|
|
265
|
-
static desc = '';
|
|
266
|
-
get_test() {
|
|
267
|
-
return {
|
|
268
|
-
method: 'get',
|
|
269
|
-
description: '',
|
|
270
|
-
path: '/get_test',
|
|
271
|
-
request: {
|
|
272
|
-
header: { 'Content-Type': 'application/json', Authorization: '' },
|
|
273
|
-
body: {},
|
|
274
|
-
query: {}
|
|
275
|
-
},
|
|
276
|
-
response: { body: {} }
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
}
|
|
340
|
+
`koa-ts-core` 提供路由元信息,配合 `koa-ts-cli` 的 `koa-ts-cli doc` 命令,可以根据 `src/controller` 自动生成 `doc` 目录下的接口配置文件,并在运行时通过 `/doc` 路由查看渲染后的接口文档。
|
|
280
341
|
|
|
281
|
-
|
|
342
|
+
具体 CLI 使用方式见:[koa-ts-cli README](../koa-ts-cli/README.md)(或对应仓库)。
|
|
282
343
|
|
|
283
|
-
|
|
344
|
+
---
|
|
284
345
|
|
|
285
|
-
|
|
346
|
+
## 日志:log4js 集成
|
|
286
347
|
|
|
287
|
-
|
|
348
|
+
配置项类型:
|
|
288
349
|
|
|
289
|
-
```
|
|
350
|
+
```ts
|
|
290
351
|
type TLog4 = boolean | ((instance: Log4js) => Log4js);
|
|
291
352
|
```
|
|
292
353
|
|
|
293
|
-
|
|
354
|
+
- 传入 `false` 或不传:不启用 log4
|
|
355
|
+
- 传入 `true`:使用默认 log4 配置
|
|
356
|
+
- 传入函数:自定义 log4 配置
|
|
357
|
+
|
|
358
|
+
默认配置示例(`log4: true`):
|
|
294
359
|
|
|
295
360
|
```js
|
|
296
361
|
log4js.configure({
|
|
297
362
|
appenders: {
|
|
298
|
-
console: { type: "console" },
|
|
363
|
+
console: { type: "console" },
|
|
299
364
|
file: {
|
|
300
365
|
type: "dateFile",
|
|
301
366
|
filename: "logs/app.log",
|
|
@@ -311,22 +376,57 @@ log4js.configure({
|
|
|
311
376
|
});
|
|
312
377
|
```
|
|
313
378
|
|
|
314
|
-
|
|
379
|
+
自定义示例:
|
|
315
380
|
|
|
316
|
-
```
|
|
381
|
+
```ts
|
|
317
382
|
initializeKoaApp({
|
|
318
383
|
log4: (instance) => {
|
|
319
384
|
return instance.configure({
|
|
320
|
-
appenders: {
|
|
321
|
-
|
|
385
|
+
appenders: { console: { type: "console" } },
|
|
386
|
+
categories: {
|
|
387
|
+
default: { appenders: ["console"], level: "debug" },
|
|
322
388
|
},
|
|
323
389
|
});
|
|
324
390
|
},
|
|
325
391
|
});
|
|
326
392
|
```
|
|
327
393
|
|
|
328
|
-
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Context 扩展
|
|
397
|
+
|
|
398
|
+
`koa-ts-core` 对 `Koa.Context` 做了类型拓展:
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
declare module "koa" {
|
|
402
|
+
interface DefaultContext {
|
|
403
|
+
// log4js 实例
|
|
404
|
+
log4?: import("log4js").Log4js;
|
|
405
|
+
|
|
406
|
+
// 生命周期 hook
|
|
407
|
+
hook?: (ctx: Koa.Context, type: "request" | "response" | "error") => void;
|
|
408
|
+
|
|
409
|
+
// 是否启用运行时日志
|
|
410
|
+
runtimeLog: boolean;
|
|
411
|
+
|
|
412
|
+
// 校验后的参数
|
|
413
|
+
validated: {
|
|
414
|
+
params: Record<string, any>;
|
|
415
|
+
get: Record<string, any>;
|
|
416
|
+
post: Record<string, any>;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// 业务扩展字段
|
|
420
|
+
extra: {
|
|
421
|
+
get: Record<string, any>;
|
|
422
|
+
post: Record<string, any>;
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
329
429
|
|
|
330
|
-
|
|
430
|
+
## 相关项目
|
|
331
431
|
|
|
332
|
-
koa
|
|
432
|
+
- [koa-ts-cli](https://www.npmjs.com/package/koa-typescript-cli):基于 `koa-ts-core` 的脚手架 CLI 工具,支持项目创建、开发、生成文档、自动生成控制器与校验器等。
|