@tyno/tyno 2.1.3

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.
Files changed (45) hide show
  1. package/README-zh.md +572 -0
  2. package/README.md +555 -0
  3. package/example/app.ts +173 -0
  4. package/example/public/index.html +1 -0
  5. package/example/public/test.json +1 -0
  6. package/package.json +63 -0
  7. package/scripts/build.mjs +97 -0
  8. package/scripts/rename-cjs.mjs +23 -0
  9. package/src/application.ts +304 -0
  10. package/src/cache/drivers/file.ts +79 -0
  11. package/src/cache/drivers/memory.ts +72 -0
  12. package/src/cache/drivers/redis.ts +72 -0
  13. package/src/cache/index.ts +5 -0
  14. package/src/cache/manager.ts +106 -0
  15. package/src/cache/types.ts +24 -0
  16. package/src/cache-facade.ts +64 -0
  17. package/src/compose.ts +139 -0
  18. package/src/context.ts +5 -0
  19. package/src/errors/app-error.ts +37 -0
  20. package/src/errors/http-error.ts +34 -0
  21. package/src/errors/index.ts +4 -0
  22. package/src/errors/runtime-error.ts +19 -0
  23. package/src/facade/index.ts +3 -0
  24. package/src/index.ts +29 -0
  25. package/src/middlewares/compress.ts +101 -0
  26. package/src/middlewares/cors.ts +57 -0
  27. package/src/middlewares/error-page.ts +89 -0
  28. package/src/middlewares/index.ts +9 -0
  29. package/src/middlewares/request-id.ts +47 -0
  30. package/src/middlewares/static.ts +138 -0
  31. package/src/mime.ts +38 -0
  32. package/src/request/body-parser.ts +61 -0
  33. package/src/request/index.ts +273 -0
  34. package/src/request/multipart-parser.ts +360 -0
  35. package/src/request-global.ts +31 -0
  36. package/src/response/sse.ts +54 -0
  37. package/src/response.ts +177 -0
  38. package/src/router/index.ts +290 -0
  39. package/src/router/node.ts +15 -0
  40. package/src/router/parse-path.ts +18 -0
  41. package/src/types.ts +109 -0
  42. package/test/functional.test.ts +614 -0
  43. package/tsconfig.build.json +13 -0
  44. package/tsconfig.cjs.json +9 -0
  45. package/tsconfig.json +21 -0
package/README-zh.md ADDED
@@ -0,0 +1,572 @@
1
+ # tyno
2
+
3
+ 零外部依赖的轻量级 Node.js HTTP 框架,TypeScript 编写,原生 `http` 模块。融合 Koa 洋葱模型与 ThinkPHP/Laravel 请求取值风格。
4
+
5
+ ```ts
6
+ import { Tyno } from '@tyno/tyno'
7
+
8
+ const app = new Tyno()
9
+ app.use((req) => `Hello ${req.query('name') || 'World'}`)
10
+ app.listen(3000)
11
+ ```
12
+
13
+ ```ts
14
+ import { Tyno } from '@tyno/tyno'
15
+ import { Router } from '@tyno/tyno/router'
16
+
17
+ const app = new Tyno()
18
+ const r = new Router()
19
+ r.get('/users/:id', (req) => ({ id: req.params.id }))
20
+ app.use(r.routes()).listen(3000)
21
+ ```
22
+
23
+ ## 特性
24
+
25
+ - **洋葱模型中间件**:返回值自动归一化,异步压缩
26
+ - **前缀树路由**:静态/参数 `:id`/正则 `:id(\d+)`/通配符 `*`,HEAD,分组 `group()`,`fallback()`
27
+ - **请求取值**:`query()` / `post()` / `param()` / `input()`
28
+ - **文件上传**:流式 multipart 状态机解析,buffer/磁盘双模
29
+ - **Response 构建**:静态工厂 `json/text/empty/redirect/image`,链式 `set/type/setStatus/setCookie/attachment`
30
+ - **全局门面**:`req/res`(Request/Response 别名)/ `request`(读入站)/ `Cache`(缓存)
31
+ - **错误体系**:`HttpError` / `RuntimeError` + 错误中间件 + Symbol 标记
32
+ - **事件系统**:`request` / `response` / `response:sent` / `error` / `ready`
33
+ - **缓存**:内存/文件/Redis,`get/set/has/delete/clear/remember`
34
+ - **内置中间件**:CORS、异步压缩、Range 静态文件、请求 ID
35
+ - **测试**:`app.inject()` 无需启动服务器
36
+
37
+ ## 模块结构
38
+
39
+ ```ts
40
+ // 主入口
41
+ import { Tyno, Response, res, Request, req } from '@tyno/tyno'
42
+
43
+ // 门面
44
+ import { request, Cache } from '@tyno/tyno/facade'
45
+
46
+ // 中间件
47
+ import { cors, compress, serveStatic, requestId } from '@tyno/tyno/middleware'
48
+
49
+ // 路由
50
+ import { Router } from '@tyno/tyno/router'
51
+
52
+ // 错误
53
+ import { HttpError, NotFound, RuntimeError } from '@tyno/tyno/errors'
54
+
55
+ // 缓存
56
+ import { CacheManager, MemoryDriver } from '@tyno/tyno/cache'
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 核心模块
62
+
63
+ ### 中间件
64
+
65
+ ```ts
66
+ // 洋葱模型
67
+ app.use(async (req, next) => {
68
+ const start = Date.now()
69
+ const res = await next()
70
+ console.log(`${req.method} ${req.path} ${res.status} ${Date.now() - start}ms`)
71
+ return res
72
+ })
73
+
74
+ // 终端中间件(不接收 next)
75
+ app.use((req) => ({ hello: 'world' }))
76
+
77
+ // 错误中间件(3 个参数或 Symbol 标记)
78
+ app.use(async (err, req, next) => {
79
+ return Response.json({ error: err.message }, err.status || 500)
80
+ })
81
+
82
+ // 批量注册(数组),依次执行
83
+ app.use([logger, auth, compress])
84
+ app.middleware([cors, requestId]) // middleware 是 use 的别名
85
+
86
+ // Router 同样支持
87
+ const r = new Router()
88
+ r.middleware([auth, adminCheck])
89
+ ```
90
+
91
+ **返回值自动归一整表**:
92
+
93
+ | 返回类型 | 自动转换 | Content-Type | 状态码 |
94
+ |---------|---------|-------------|--------|
95
+ | `Response` 实例 | 原样使用 | 已有 | 已有 |
96
+ | `string` | `Response.text(str)` | `text/plain; charset=utf-8` | 200 |
97
+ | `Buffer` | `Response.text(buf)` | `text/plain; charset=utf-8` | 200 |
98
+ | `object`(普通对象) | `Response.json(obj)` | `application/json; charset=utf-8` | 200 |
99
+ | `number` / `boolean` | `Response.text(String(v))` | `text/plain; charset=utf-8` | 200 |
100
+ | `ReadableStream`(有 `pipe`) | 流式输出 | 无(透传) | 200 |
101
+ | `AsyncIterable` / `Generator` | SSE 流式输出 | `text/event-stream` | 200 |
102
+ | `null` / `undefined` | `Response.empty()` | 无 | 204 |
103
+ | `Image Buffer`(手动) | `Response.image(buf, 'png')` | `image/png` | 200 |
104
+
105
+ > 返回图片需显式调用 `Response.image()`,框架不会自动区分 Buffer 是文本还是图片。
106
+
107
+ ### Request
108
+
109
+ Proxy 封装原生 `IncomingMessage`,内置属性只读,自定义属性写入 `DATA_KEY`。
110
+
111
+ ```ts
112
+ // 基本信息
113
+ req.method // GET / POST
114
+ req.path // 不含 query string
115
+ req.url // 含 query string
116
+ req.ip // 优先 X-Forwarded-For
117
+ req.protocol // http / https
118
+ req.secure // 是否 HTTPS
119
+ req.fullUrl // protocol://host/url
120
+ req.httpVersion // 1.1 / 2.0
121
+ req.host // 含端口
122
+ req.hostname // 不含端口
123
+ req.userAgent // User-Agent
124
+ req.isAjax() // X-Requested-With 判断
125
+ req.wantsJSON() // Accept 头判断
126
+
127
+ // Query
128
+ req.query() // → { id: '1', name: 'Alice' }
129
+ req.query('id') // → '1'
130
+ req.query('x', 'def') // → 'def' 带默认值
131
+ req.get('id') // 别名
132
+ req.getQuery('id') // 别名
133
+
134
+ // Body(惰性解析,结果缓存)
135
+ await req.body() // JSON / urlencoded / text 自动识别
136
+ await req.post('title') // 取 POST 字段
137
+ await req.param('id') // GET 优先,fallback POST
138
+ await req.input('id', 0, parseInt) // 带 filter
139
+
140
+ // Header / Cookie
141
+ req.header('authorization') // 大小写不敏感
142
+ req.cookies // 含 URL 解码
143
+ req.getCookie('sessionId')
144
+
145
+ // 文件上传(流式 multipart)
146
+ await req.files() // → { fields, files }
147
+ await req.file('avatar') // → 单个 UploadedFile | null
148
+ // UploadedFile: { fieldname, filename, mimetype, filepath, size, buffer? }
149
+ ```
150
+
151
+ **Body 配置**:
152
+
153
+ ```ts
154
+ const app = new Tyno({
155
+ body: {
156
+ limit: '2mb', // JSON/urlencoded/text 大小限制
157
+ uploadLimit: '20mb', // 文件上传总大小限制
158
+ uploadBufferLimit: '512kb',// 文件内存阈值,超过流式写磁盘
159
+ uploadDir: '/tmp',
160
+ keepExtensions: true
161
+ }
162
+ })
163
+ ```
164
+
165
+ ### Response
166
+
167
+ 实例构造 + 静态工厂 + 静态门面(预设 header/cookie),三位一体。
168
+
169
+ **别名**:`Response`(类名)= `response` = `res`
170
+
171
+ ```ts
172
+ import { Response, res } from '@tyno/tyno'
173
+
174
+ // —— 静态工厂 ——
175
+ Response.json({ ok: true }) // 200 application/json; charset=utf-8
176
+ Response.json(data, 201) // 自定义状态码
177
+ Response.text('hello') // 200 text/plain; charset=utf-8
178
+ Response.empty() // 204 无 Content-Type
179
+ Response.empty(201) // 201
180
+ Response.redirect('/new') // 302 Location: /new
181
+ Response.redirect('/new', 301) // 301
182
+ Response.image(buf) // 200 image/png
183
+ Response.image(buf, 'jpeg') // 200 image/jpeg
184
+ Response.image(buf, 'image/webp') // 200 image/webp
185
+ Response.image(buf, 'svg', 201) // 201 image/svg+xml
186
+
187
+ // —— 实例构造 ——
188
+ new Response(200, { 'X-Custom': 'yes' }, 'body')
189
+ new Response(404, {}, 'Not Found')
190
+
191
+ // —— 链式修改 ——
192
+ Response.json({ ok: true })
193
+ .set('X-Request-Id', 'abc')
194
+ .type('json')
195
+ .setStatus(201)
196
+ .setCookie('token', 'xxx', { httpOnly: true, maxAge: 3600, sameSite: 'Lax' })
197
+ .clearCookie('old_session')
198
+ .attachment('report.txt') // Content-Disposition: attachment
199
+
200
+ // —— 静态门面(预设,final 优先) ——
201
+ app.use(async (req, next) => {
202
+ Response.header('X-Powered-By', 'tyno')
203
+ Response.setCookie('track', 'abc', { httpOnly: true })
204
+ return next()
205
+ })
206
+ ```
207
+
208
+ **Response 输出类型完整参考**:
209
+
210
+ | 工厂方法 | Content-Type | 默认状态码 | body 类型 |
211
+ |---------|-------------|-----------|----------|
212
+ | `json(data, status?)` | `application/json; charset=utf-8` | 200 | `string` (JSON) |
213
+ | `text(data, status?)` | `text/plain; charset=utf-8` | 200 | `string` |
214
+ | `empty(status?)` | 无 | 204 | `null` |
215
+ | `redirect(url, status?)` | 无 (`Location` 头) | 302 | `null` |
216
+ | `image(data, type?, status?)` | `image/*` | 200 | `Buffer \| string` |
217
+ | `new Response(s, h, b)` | 手动指定 | 手动指定 | `ResponseBody` |
218
+
219
+ | `ResponseBody` 类型 | 输出行为 |
220
+ |-------------------|---------|
221
+ | `string` | `nodeRes.end(body)` |
222
+ | `Buffer` | `nodeRes.end(body)` |
223
+ | `NodeJS.ReadableStream` | `body.pipe(nodeRes)` |
224
+ | `AsyncIterable` | SSE 流式逐块写入 |
225
+ | `null` | `nodeRes.end()` |
226
+
227
+ ### 路由
228
+
229
+ 前缀树实现,支持参数约束、通配符、路由分组和 fallback。
230
+
231
+ ```ts
232
+ import { Router } from '@tyno/tyno/router'
233
+
234
+ const r = new Router({ prefix: '/api' })
235
+
236
+ // 路由级中间件(支持单个或数组,use / middleware 等价)
237
+ r.use(async (req, next) => {
238
+ req.user = { id: 1 }
239
+ return next()
240
+ })
241
+ r.middleware([auth, adminCheck]) // 数组批量注册
242
+ })
243
+
244
+ // 方法注册
245
+ r.get('/users/:id(\\d+)', (req) => ({ id: req.params.id }))
246
+ r.head('/health', () => Response.empty(200))
247
+ r.post('/users', async (req) => ({ created: await req.body() }))
248
+ r.put('/users/:id', async (req) => Response.empty(204))
249
+ r.delete('/users/:id', () => Response.empty(204))
250
+ r.patch('/users/:id', async (req) => 'ok')
251
+ r.all('/any', (req) => `${req.method} matched`)
252
+ r.get('/*', (req) => ({ wild: req.params['*'] }))
253
+
254
+ // 路由分组
255
+ r.group('/admin', (admin) => {
256
+ admin.get('/dashboard', () => 'Admin Panel')
257
+ admin.get('/users', () => 'User List')
258
+ // 等价于 /api/admin/dashboard、/api/admin/users
259
+ })
260
+
261
+ // fallback:所有路由都不匹配时调用
262
+ r.fallback((req) => Response.json({ error: 'Not Found' }, 404))
263
+
264
+ app.use(r.routes())
265
+ ```
266
+
267
+ **路径模式**:
268
+
269
+ | 模式 | 含义 | 匹配 |
270
+ |------|------|------|
271
+ | `users` | 静态段 | `/api/users` |
272
+ | `:id` | 参数段 | `/api/123` |
273
+ | `:id(\d+)` | 正则约束 | `/api/123` |
274
+ | `*` | 通配符 | `/api/a/b/c` |
275
+
276
+ 优先级:**静态 > 参数 > 通配符**。支持 `get/head/post/put/delete/patch/all`。
277
+
278
+ ---
279
+
280
+ ## 错误 & 事件
281
+
282
+ ### 错误处理
283
+
284
+ ```ts
285
+ import { HttpError, NotFound, RuntimeError, isRuntimeError } from '@tyno/tyno/errors'
286
+
287
+ // HttpError:4xx 默认 expose,5xx 默认隐藏
288
+ throw new NotFound('资源不存在')
289
+ throw new HttpError(401, '请先登录')
290
+
291
+ // RuntimeError:默认 500 不暴露,带 cause 链
292
+ throw new RuntimeError('数据库连接失败', {
293
+ code: 'DB_FAIL',
294
+ cause: new Error('ECONNREFUSED')
295
+ })
296
+ ```
297
+
298
+ **快捷类**:`BadRequest`、`Unauthorized`、`Forbidden`、`NotFound`、`Conflict`、`PayloadTooLarge`、`TooManyRequests`、`InternalServerError`。
299
+
300
+ **错误中间件**(参数 >= 3 或 `IS_ERROR_MIDDLEWARE` 标记,按注册顺序串联):
301
+
302
+ ```ts
303
+ import { asErrorMiddleware } from '@tyno/tyno'
304
+
305
+ app.use(async (err, req, next) => {
306
+ if (isRuntimeError(err)) {
307
+ return Response.json({ error: '服务不可用', code: err.code }, 500)
308
+ }
309
+ return Response.json({ error: err.message }, err.status || 500)
310
+ })
311
+ ```
312
+
313
+ ### 事件系统
314
+
315
+ Application 继承 EventEmitter,完整生命周期事件:
316
+
317
+ | 事件 | 触发时机 | 参数 |
318
+ |------|---------|------|
319
+ | `request` | 请求到达,compose 之前,`AsyncLocalStorage` 就绪 | `(req)` |
320
+ | `response` | compose 完成,响应发送**前** | `(req, res)` |
321
+ | `response:sent` | 响应发送**后** | `(req, res)` |
322
+ | `error` | 异常发生(有监听器时触发) | `(err, req)` |
323
+ | `ready` | 服务启动成功 | `()` |
324
+
325
+ ```ts
326
+ import { Tyno } from '@tyno/tyno'
327
+ const app = new Tyno({ debug: true })
328
+
329
+ app.on('request', (req) => {
330
+ console.log(`→ ${req.method} ${req.path}`)
331
+ })
332
+
333
+ app.on('response', (req, res) => {
334
+ // 响应发送前:可记录日志
335
+ console.log(`← ${req.path} ${res.status}`)
336
+ })
337
+
338
+ app.on('response:sent', (req, res) => {
339
+ // 响应已发送:可记录耗时、清理资源
340
+ console.log(`✓ ${req.path} ${res.status} done`)
341
+ })
342
+
343
+ app.on('error', (err, req) => {
344
+ // 需注册监听器才触发,遵循 Node.js EventEmitter 语义
345
+ logger.error({ err, path: req.path })
346
+ })
347
+
348
+ app.on('ready', () => {
349
+ console.log('Server ready')
350
+ })
351
+ ```
352
+
353
+ ---
354
+
355
+ ## 内置中间件
356
+
357
+ ```ts
358
+ import { cors, compress, serveStatic, requestId } from '@tyno/tyno/middleware'
359
+
360
+ // CORS
361
+ app.use(cors())
362
+ app.use(cors({ origin: ['https://a.com'], credentials: true, maxAge: 86400 }))
363
+
364
+ // 异步压缩(gzip/deflate),支持流式响应,跳过图片/视频
365
+ app.use(compress())
366
+ app.use(compress({ threshold: 2048, level: 6 }))
367
+
368
+ // 静态文件:ETag/304、Range/206、路径穿越防护
369
+ app.use(serveStatic('./public', { prefix: '/static', maxAge: 3600 }))
370
+
371
+ // 请求 ID:自动注入 req.requestId,写入响应头
372
+ app.use(requestId())
373
+ app.use(requestId({ readFromHeader: false }))
374
+
375
+ // 开发错误页
376
+ new Tyno({ debug: true }) // 自动附加,显示堆栈和 cause,支持 JSON/HTML
377
+ ```
378
+
379
+ ---
380
+
381
+ ## 缓存
382
+
383
+ 内存 / 文件 / Redis 三种驱动,统一 `get/set/has/delete/clear/remember` API。
384
+
385
+ **别名**:`Cache`(首字母大写)= `cache`(小写)
386
+
387
+ ```ts
388
+ import { Cache } from '@tyno/tyno/facade'
389
+
390
+ const app = new Tyno({ cache: { driver: 'memory', prefix: 'my:', ttl: 3600 } })
391
+
392
+ // 通过 app
393
+ await app.cache().set('user:1', { name: 'Alice' }, 60)
394
+ const user = await app.cache().get('user:1')
395
+
396
+ // remember:不存在时调用 factory
397
+ const config = await app.cache().remember('config', 3600, loadConfig)
398
+
399
+ // 全局门面(中间件内)
400
+ app.use(async () => {
401
+ await Cache.set('key', 'value')
402
+ return Response.json({ ok: true })
403
+ })
404
+ ```
405
+
406
+ **文件驱动**:`{ driver: 'file', file: { path: './storage/cache' } }`
407
+
408
+ **Redis 驱动**:需 `npm install redis`,`{ driver: 'redis', redis: { host, port } }`
409
+
410
+ ---
411
+
412
+ ## 其他
413
+
414
+ ### 初始化器
415
+
416
+ ```ts
417
+ app.initialize(async (app) => { await db.connect() })
418
+ app.listen(4567) // 自动先执行
419
+ ```
420
+
421
+ ### HTTPS
422
+
423
+ ```ts
424
+ app.listen({ port: 443, tls: { key, cert } })
425
+ ```
426
+
427
+ ### 测试
428
+
429
+ ```ts
430
+ const res = await app.inject({ method: 'GET', url: '/?name=alice' })
431
+ res.status // 200
432
+ res.json() // { hello: 'alice' }
433
+ ```
434
+
435
+ ### SSE
436
+
437
+ ```ts
438
+ async function* gen() { yield 'chunk1'; yield 'chunk2' }
439
+ return gen()
440
+ ```
441
+
442
+ ---
443
+
444
+ ## API 速览
445
+
446
+ ### Application
447
+
448
+ | 方法 | 说明 |
449
+ |------|------|
450
+ | `new Tyno({ debug?, body?, cache? })` | 创建应用 |
451
+ | `use(fn\|[...fn])` / `middleware(fn\|[...fn])` | 注册中间件,支持数组 |
452
+ | `initialize(fn)` | 注册初始化器 |
453
+ | `listen(port\|opts)` | 启动 HTTP/HTTPS |
454
+ | `close()` | 优雅关闭 |
455
+ | `inject(opts)` | 测试请求 |
456
+ | `cache()` | 缓存实例 |
457
+ | `on/emit/once` | EventEmitter |
458
+
459
+ ### Router
460
+
461
+ | 方法 | 说明 |
462
+ |------|------|
463
+ | `new Router({ prefix? })` | 创建路由 |
464
+ | `use(fn\|[...fn])` / `middleware(fn\|[...fn])` | 路由级中间件,支持数组 |
465
+ | `get/head/post/put/delete/patch/all(path, ...h)` | 注册路由 |
466
+ | `group(prefix, fn)` | 路由分组 |
467
+ | `fallback(handler)` | 未匹配时的回退处理 |
468
+ | `routes()` | 返回中间件 |
469
+
470
+ ### Request
471
+
472
+ | 别名 | 导出来源 |
473
+ |------|---------|
474
+ | `Request`(类名)= `req` | `tyno` |
475
+ | `request`(全局门面) | `tyno/facade` |
476
+
477
+ | 方法 | 说明 |
478
+ |------|------|
479
+ | `query()/query(k)/get(k)` | Query 参数 |
480
+ | `body()/post/param/input` | Body 取值 |
481
+ | `files()/file(name)` | 文件上传 |
482
+ | `header/cookies/getCookie` | 请求头 |
483
+
484
+ ### Response
485
+
486
+ | 别名 | 导出来源 |
487
+ |------|---------|
488
+ | `Response`(类名)= `response` = `res` | `tyno` |
489
+
490
+ | 静态工厂 | Content-Type | 状态码 |
491
+ |---------|-------------|--------|
492
+ | `json(data, status?)` | `application/json` | 200 |
493
+ | `text(data, status?)` | `text/plain` | 200 |
494
+ | `empty(status?)` | — | 204 |
495
+ | `redirect(url, status?)` | `Location` 头 | 302 |
496
+ | `image(data, type?, status?)` | `image/*` | 200 |
497
+
498
+ | 实例/静态方法 | 说明 |
499
+ |-------------|------|
500
+ | `set/setHeader/type/setStatus` | 链式修改 |
501
+ | `setCookie/clearCookie/attachment` | Cookie + 下载 |
502
+ | `get(name)` | 读取响应头 |
503
+ | `static header(name, value)` | 预设响应头(门面) |
504
+ | `static setCookie(name, value, options?)` | 预设 Cookie(门面) |
505
+
506
+ ### 错误类
507
+
508
+ | 类 | 说明 |
509
+ |----|------|
510
+ | `AppError` | 基类 |
511
+ | `HttpError(status, msg?, props?)` | HTTP 错误 |
512
+ | `RuntimeError(msg, opts?)` | 运行时错误 |
513
+ | `NotFound/Forbidden/...` | 快捷子类 |
514
+
515
+ ### 全局门面
516
+
517
+ | 导出 | 来源 | 说明 |
518
+ |------|------|------|
519
+ | `req` | `tyno` | Request 类别名 |
520
+ | `res` | `tyno` | Response 类别名 |
521
+ | `request` | `tyno/facade` | 读取当前请求 |
522
+ | `Cache / cache` | `tyno/facade` | 缓存操作 |
523
+
524
+ ---
525
+
526
+ ## 项目结构
527
+
528
+ ```
529
+ tyno/
530
+ ├── src/
531
+ │ ├── index.ts # 主入口
532
+ │ ├── application.ts # Application (EventEmitter)
533
+ │ ├── compose.ts # 洋葱模型 + 错误处理
534
+ │ ├── context.ts # AsyncLocalStorage
535
+ │ ├── types.ts # 类型定义
536
+ │ ├── response.ts # Response 类
537
+ │ ├── response/sse.ts # SSEStream
538
+ │ ├── request/
539
+ │ │ ├── index.ts # Request 类
540
+ │ │ ├── body-parser.ts # BodyParser
541
+ │ │ └── multipart-parser.ts # 流式 multipart
542
+ │ ├── router/
543
+ │ │ ├── index.ts # Router + group + fallback
544
+ │ │ ├── node.ts # TrieNode
545
+ │ │ └── parse-path.ts # 路径解析
546
+ │ ├── errors/
547
+ │ │ ├── app-error.ts # AppError
548
+ │ │ ├── http-error.ts # HttpError
549
+ │ │ └── runtime-error.ts # RuntimeError
550
+ │ ├── middlewares/
551
+ │ │ ├── index.ts # 子路径导出
552
+ │ │ ├── cors.ts # CORS
553
+ │ │ ├── compress.ts # 异步压缩
554
+ │ │ ├── static.ts # 静态文件 + Range
555
+ │ │ ├── request-id.ts # 请求 ID
556
+ │ │ └── error-page.ts # 开发错误页
557
+ │ ├── cache/
558
+ │ │ ├── index.ts / manager.ts / types.ts
559
+ │ │ └── drivers/ (memory / file / redis)
560
+ │ ├── facade/ # 门面子路径导出
561
+ │ ├── request-global.ts # 全局 request
562
+ │ ├── cache-facade.ts # 全局 cache
563
+ │ └── mime.ts
564
+ ├── example/app.ts
565
+ ├── test/functional.test.ts
566
+ ├── package.json
567
+ └── tsconfig.json
568
+ ```
569
+
570
+ ## License
571
+
572
+ MIT