@faapi/faapi 1.4.0 → 2.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.
@@ -0,0 +1,232 @@
1
+ import { F as FaapiContext, a as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions, L as LoggerOptions } from './routeTypes-FtbRkpVF.js';
2
+ import { Server } from 'node:http';
3
+ import { WebSocket } from 'ws';
4
+
5
+ /**
6
+ * 测试专用:从选项对象创建 FaapiContext,免去手写 `new Request(url)` 的样板代码
7
+ *
8
+ * 与 createContext 的关系:createTestContext 内部构造 Request 后调 createContext,
9
+ * 语义完全一致,仅是测试场景的语法糖——不写无意义的 host、query 用对象形式、headers 直接传对象。
10
+ *
11
+ * 为什么不合并进 createContext:createContext 运行时也从真实 HTTP 请求构造 Request,
12
+ * 保持 `(request: Request)` 签名使运行时与测试同构;createTestContext 是纯测试便捷封装,
13
+ * 不引入运行时分支。
14
+ *
15
+ * body 不在此处理:createContext 本身不读 `request.body`,body 注入由 `invokeHandler`
16
+ * 的第 3 个参数负责。POST/PUT/PATCH 测试时 body 单独传给 invokeHandler,避免在两处传 body 产生混淆。
17
+ *
18
+ * @param options 请求选项(path 必填,其余可选)
19
+ * @returns FaapiContext
20
+ */
21
+ declare function createTestContext(options: CreateTestContextOptions): FaapiContext;
22
+ /**
23
+ * createTestContext 的选项
24
+ */
25
+ interface CreateTestContextOptions {
26
+ /** 请求方法,默认 'GET' */
27
+ method?: string;
28
+ /** 请求路径,必填,如 '/api/user'(无需写 host) */
29
+ path: string;
30
+ /** 查询参数,对象形式,自动拼接到 URL(值会被 String() 转换;数组生成同名多值参数) */
31
+ query?: Record<string, string | number | boolean | Array<string | number | boolean>>;
32
+ /** 请求头 */
33
+ headers?: Record<string, string>;
34
+ /** 动态路由参数,默认 {} */
35
+ params?: Record<string, string>;
36
+ /** 业务配置(来自 faapi.config.ts),默认 {} */
37
+ config?: Record<string, unknown>;
38
+ /** 客户端 IP,默认 '' */
39
+ ip?: string;
40
+ }
41
+
42
+ /**
43
+ * 调用路由 handler 并将返回值转为 Response
44
+ *
45
+ * 流程(洋葱模型):
46
+ * 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after
47
+ * 2. 中间件不调用 next() 即拦截请求(必须返回 Response)
48
+ * 3. 中间件可用 try/catch 捕获内层错误
49
+ * 4. 最内层执行注入器(按需)→ handler
50
+ *
51
+ * 注入器与中间件解耦:
52
+ * - 注入器按 handler 参数名匹配,只执行需要的
53
+ * - 注入器可读取中间件塞进 ctx 的值
54
+ */
55
+ declare function invokeHandler(handler: (...args: unknown[]) => unknown, ctx: FaapiContext, body?: unknown, middlewares?: FaapiMiddleware[], injectors?: InjectorMap): Promise<Response>;
56
+
57
+ /**
58
+ * createTestServer 入参
59
+ *
60
+ * 业务方一行代码启动带 schema 校验的 E2E 测试服务器。
61
+ * 详见 src/testServer.md。
62
+ */
63
+ interface TestServerOptions {
64
+ /** 项目根目录(路由源码所在,必填) */
65
+ rootDir: string;
66
+ patterns?: string[];
67
+ /**
68
+ * schema 产物输出目录(绝对路径或相对 rootDir)。
69
+ * 不传时自动 mkdtemp 生成临时目录,close() 时清理。
70
+ * 传值时 close() 仍会清理该目录。
71
+ */
72
+ dist?: string;
73
+ /** CORS 中间件配置,默认 false(禁用,避免污染断言) */
74
+ cors?: CorsOptions | boolean;
75
+ /** 安全头配置,默认 false */
76
+ helmet?: HelmetOptions | boolean;
77
+ /** 请求日志配置,默认 false(避免污染测试输出) */
78
+ logger?: LoggerOptions | boolean;
79
+ /** 全局中间件(外层洋葱) */
80
+ middlewares?: FaapiMiddleware[];
81
+ /** 全局注入器 */
82
+ injectors?: InjectorMap;
83
+ /** 请求错误钩子(在错误响应生成后调用,用于副作用) */
84
+ onError?: (error: unknown, ctx: FaapiContext) => Promise<void> | void;
85
+ /** 业务配置(注入到 ctx.config) */
86
+ config?: Record<string, unknown>;
87
+ /** 请求体大小限制(字节),默认 10MB */
88
+ bodyLimit?: number;
89
+ }
90
+ /**
91
+ * createTestServer 返回值
92
+ *
93
+ * 业务方通过 baseUrl 发 fetch 请求,close() 一行完成 teardown。
94
+ */
95
+ interface TestServer {
96
+ /** Node.js HTTP Server 实例(已 listen) */
97
+ server: Server;
98
+ /** 形如 http://localhost:<随机端口> */
99
+ baseUrl: string;
100
+ /** 排序后的路由清单 */
101
+ routes: RouteManifest;
102
+ /** WebSocket 路由清单 */
103
+ wsRoutes: WsRouteManifest;
104
+ /** schema 临时目录绝对路径(业务方调试时可查看生成的 zod.js) */
105
+ schemaDist: string;
106
+ /**
107
+ * 关闭 server + 清理 schema 目录 + 清空 schema 模块缓存
108
+ *
109
+ * 内部顺序:
110
+ * 1. server.closeAllConnections?.()(Node 18+,强制断开 WS / 长连接)
111
+ * 2. server.close()
112
+ * 3. fs.rm(schemaDist, { recursive, force })
113
+ * 4. invalidateSchemaCache()
114
+ *
115
+ * 幂等:重复调用不会重复清理。
116
+ */
117
+ close(): Promise<void>;
118
+ }
119
+ /**
120
+ * 一键启动带 schema 校验的 E2E 测试服务器
121
+ *
122
+ * 内部流程:
123
+ * 1. scanRoutes 扫描路由
124
+ * 2. sortRoutes 排序
125
+ * 3. mkdtemp 创建临时 schema 目录(或用传入的 dist)
126
+ * 4. generateSchemaFiles 生成 zod.js
127
+ * 5. createServer 创建 server(默认禁用 CORS/Helmet/Logger,避免污染断言)
128
+ * 6. server.listen(0) 随机端口
129
+ * 7. 返回 TestServer
130
+ *
131
+ * 详见 src/testServer.md。
132
+ *
133
+ * @param options rootDir 必填,其余可选
134
+ * @returns TestServer 实例
135
+ */
136
+ declare function createTestServer(options: TestServerOptions): Promise<TestServer>;
137
+
138
+ /**
139
+ * WebSocket 测试客户端
140
+ *
141
+ * 公开导出 connectWs + MessageQueue + waitForWsOpen,业务方测试 WS 路由时
142
+ * 免去手写"消息竞态防护 + 三事件监听 + 端口拼接"样板代码。
143
+ *
144
+ * 详见 src/wsTestClient.md。
145
+ */
146
+ /**
147
+ * connectWs 入参
148
+ */
149
+ interface WsTestClientOptions {
150
+ /** 等待 open 的超时(ms),默认 2000 */
151
+ timeout?: number;
152
+ /** 握手请求头(如 authorization) */
153
+ headers?: Record<string, string>;
154
+ /** WS 子协议 */
155
+ protocols?: string | string[];
156
+ }
157
+ /**
158
+ * connectWs 返回值
159
+ *
160
+ * 业务方通过 ws.send() 发消息,queue.next() 取消息,close() 关闭。
161
+ */
162
+ interface WsTestClient {
163
+ /** ws 库原生实例,业务方可直接 ws.send() / ws.close() */
164
+ ws: WebSocket;
165
+ /** 已开始缓冲的消息队列,调 next(timeout?) 取下一条 */
166
+ queue: MessageQueue;
167
+ /**
168
+ * 关闭 ws 并等待 'close' 事件
169
+ *
170
+ * 内部:
171
+ * 1. 若 ws 仍 OPEN/CLOSING,调 ws.close()
172
+ * 2. 等待 'close' 事件(超时 1000ms 强制 resolve)
173
+ *
174
+ * 幂等:重复调用不抛错。
175
+ */
176
+ close(): Promise<void>;
177
+ }
178
+ /**
179
+ * 消息队列:避免 once('message') 与服务端 onOpen 推送的竞态
180
+ *
181
+ * 服务端在 handleUpgrade 回调里同步触发 onOpen 并 send('connected'),
182
+ * 客户端 'open' 事件触发后到注册 once('message') 之间存在窗口,
183
+ * 若 'connected' 在此窗口内到达,once 会错过。
184
+ *
185
+ * 队列在创建 ws 时立即监听 'message',按 FIFO 顺序消费。
186
+ */
187
+ declare class MessageQueue {
188
+ private queue;
189
+ private waiters;
190
+ private listener;
191
+ constructor(ws: WebSocket);
192
+ /**
193
+ * 取下一条消息
194
+ *
195
+ * 队列有则立即 resolve,无则注册 waiter 等待下一条 'message' 事件。
196
+ * 超时未到 → reject('WebSocket message timeout'),waiter 被清理。
197
+ *
198
+ * @param timeout 超时毫秒,默认 2000
199
+ */
200
+ next(timeout?: number): Promise<string>;
201
+ }
202
+ /**
203
+ * Promise 化等待 ws 'open' 事件
204
+ *
205
+ * 同时监听 'open' / 'error' / 'close' 三事件,任一触发都清理 timer,
206
+ * 避免 timer 泄漏。
207
+ *
208
+ * @param ws WebSocket 实例
209
+ * @param timeout 超时毫秒,默认 2000
210
+ * @returns 'open' → resolve;'error' → reject(err);'close' → reject;超时 → reject
211
+ */
212
+ declare function waitForWsOpen(ws: WebSocket, timeout?: number): Promise<void>;
213
+ /**
214
+ * 一键连接 WS server
215
+ *
216
+ * 内部流程:
217
+ * 1. baseUrl 协议转换(http → ws,https → wss)
218
+ * 2. new WebSocket(url, protocols, { headers })
219
+ * 3. 立即创建 MessageQueue(开始缓冲消息,避免竞态)
220
+ * 4. waitForWsOpen 等待连接建立(三事件监听 + 超时清理)
221
+ * 5. 返回 WsTestClient
222
+ *
223
+ * 连接失败(中间件拦截 / 路径未匹配 / 超时)→ reject。
224
+ *
225
+ * @param baseUrl createTestServer().baseUrl(http://...)
226
+ * @param pathname WS 路径,如 '/api/chat',可含 query
227
+ * @param options timeout / headers / protocols
228
+ * @returns WsTestClient 实例
229
+ */
230
+ declare function connectWs(baseUrl: string, pathname: string, options?: WsTestClientOptions): Promise<WsTestClient>;
231
+
232
+ export { type CreateTestContextOptions, FaapiContext, FaapiMiddleware, InjectorMap, MessageQueue, type TestServer, type TestServerOptions, type WsTestClient, type WsTestClientOptions, connectWs, createTestContext, createTestServer, invokeHandler, waitForWsOpen };