vafast 0.3.10 → 0.4.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.
Files changed (43) hide show
  1. package/README.md +61 -0
  2. package/dist/defineRoute.d.ts +101 -3
  3. package/dist/defineRoute.js +30 -1
  4. package/dist/defineRoute.js.map +1 -1
  5. package/dist/index.d.ts +4 -2
  6. package/dist/index.js +363 -50
  7. package/dist/index.js.map +1 -1
  8. package/dist/monitoring/index.js +18 -1
  9. package/dist/monitoring/index.js.map +1 -1
  10. package/dist/monitoring/native-monitor.js +18 -1
  11. package/dist/monitoring/native-monitor.js.map +1 -1
  12. package/dist/node-server/index.js +46 -2
  13. package/dist/node-server/index.js.map +1 -1
  14. package/dist/node-server/serve.d.ts +16 -1
  15. package/dist/node-server/serve.js +46 -2
  16. package/dist/node-server/serve.js.map +1 -1
  17. package/dist/router/index.js +2 -1
  18. package/dist/router/index.js.map +1 -1
  19. package/dist/router.js +2 -1
  20. package/dist/router.js.map +1 -1
  21. package/dist/serve.js +46 -2
  22. package/dist/serve.js.map +1 -1
  23. package/dist/server/index.js +18 -1
  24. package/dist/server/index.js.map +1 -1
  25. package/dist/server/server-factory.js +18 -1
  26. package/dist/server/server-factory.js.map +1 -1
  27. package/dist/server/server.d.ts +15 -1
  28. package/dist/server/server.js +18 -1
  29. package/dist/server/server.js.map +1 -1
  30. package/dist/types/types.d.ts +12 -0
  31. package/dist/utils/create-handler.d.ts +12 -3
  32. package/dist/utils/create-handler.js +2 -1
  33. package/dist/utils/create-handler.js.map +1 -1
  34. package/dist/utils/index.d.ts +4 -1
  35. package/dist/utils/index.js +224 -1
  36. package/dist/utils/index.js.map +1 -1
  37. package/dist/utils/route-registry.d.ts +134 -0
  38. package/dist/utils/route-registry.js +128 -0
  39. package/dist/utils/route-registry.js.map +1 -0
  40. package/dist/utils/sse.d.ts +87 -0
  41. package/dist/utils/sse.js +181 -0
  42. package/dist/utils/sse.js.map +1 -0
  43. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ import { RouteSchema, HandlerContext } from '../types/schema.js';
2
+ import '@sinclair/typebox';
3
+
4
+ /**
5
+ * Server-Sent Events (SSE) 支持
6
+ *
7
+ * 用于实现流式响应,如 AI 聊天、实时通知等
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { createSSEHandler, Type } from 'vafast'
12
+ *
13
+ * const streamHandler = createSSEHandler(
14
+ * { query: Type.Object({ prompt: Type.String() }) },
15
+ * async function* ({ query }) {
16
+ * yield { event: 'start', data: { message: 'Starting...' } }
17
+ *
18
+ * for await (const chunk of aiStream(query.prompt)) {
19
+ * yield { data: chunk }
20
+ * }
21
+ *
22
+ * yield { event: 'end', data: { message: 'Done!' } }
23
+ * }
24
+ * )
25
+ * ```
26
+ */
27
+
28
+ /**
29
+ * SSE 事件类型
30
+ */
31
+ interface SSEEvent<T = unknown> {
32
+ /** 事件名称(可选,默认为 message) */
33
+ event?: string;
34
+ /** 事件数据 */
35
+ data: T;
36
+ /** 事件 ID(可选) */
37
+ id?: string;
38
+ /** 重试间隔(毫秒,可选) */
39
+ retry?: number;
40
+ }
41
+ /**
42
+ * SSE 生成器函数类型
43
+ */
44
+ type SSEGenerator<T extends RouteSchema = RouteSchema> = (ctx: HandlerContext<T>) => AsyncGenerator<SSEEvent<unknown>, void, unknown>;
45
+ /**
46
+ * SSE 标记类型 - 使用字面量品牌类型
47
+ */
48
+ type SSEMarker = {
49
+ readonly __brand: 'SSE';
50
+ };
51
+ /**
52
+ * SSE Handler 类型标记
53
+ */
54
+ interface SSEHandler<TSchema extends RouteSchema = RouteSchema> {
55
+ (req: Request): Promise<Response>;
56
+ /** 返回类型标记 - SSE 流的数据类型 */
57
+ readonly __returnType: unknown;
58
+ /** Schema 类型标记 */
59
+ readonly __schema: TSchema;
60
+ /** SSE 标记 - 使用品牌类型确保不被扩展 */
61
+ readonly __sse: SSEMarker;
62
+ }
63
+ /**
64
+ * 创建 SSE 流式响应处理器
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // 基础用法
69
+ * const streamChat = createSSEHandler(
70
+ * { query: Type.Object({ message: Type.String() }) },
71
+ * async function* ({ query }) {
72
+ * const response = await ai.chat(query.message);
73
+ *
74
+ * for await (const chunk of response) {
75
+ * yield { data: { text: chunk } };
76
+ * }
77
+ * }
78
+ * );
79
+ *
80
+ * // 使用路由
81
+ * route('GET', '/chat/stream', streamChat)
82
+ * ```
83
+ */
84
+ declare function createSSEHandler<const T extends RouteSchema>(schema: T, generator: SSEGenerator<T>): SSEHandler<T>;
85
+ declare function createSSEHandler(generator: SSEGenerator<RouteSchema>): SSEHandler<RouteSchema>;
86
+
87
+ export { type SSEEvent, type SSEGenerator, type SSEHandler, type SSEMarker, createSSEHandler };
@@ -0,0 +1,181 @@
1
+ // src/utils/parsers.ts
2
+ import qs from "qs";
3
+ import cookie from "cookie";
4
+ function extractQueryString(url) {
5
+ const qIndex = url.indexOf("?");
6
+ if (qIndex === -1) return "";
7
+ const hashIndex = url.indexOf("#", qIndex);
8
+ return hashIndex === -1 ? url.substring(qIndex + 1) : url.substring(qIndex + 1, hashIndex);
9
+ }
10
+ function parseQuery(req) {
11
+ const queryString = extractQueryString(req.url);
12
+ if (!queryString) return {};
13
+ return qs.parse(queryString);
14
+ }
15
+ function parseHeaders(req) {
16
+ const headers = /* @__PURE__ */ Object.create(null);
17
+ req.headers.forEach((value, key) => {
18
+ headers[key] = value;
19
+ });
20
+ return headers;
21
+ }
22
+ function parseCookies(req) {
23
+ const cookieHeader = req.headers.get("cookie");
24
+ if (!cookieHeader) return {};
25
+ try {
26
+ const parsed = cookie.parse(cookieHeader);
27
+ const result = {};
28
+ for (const [key, value] of Object.entries(parsed)) {
29
+ if (value !== void 0 && value !== null) {
30
+ result[key] = value;
31
+ }
32
+ }
33
+ return result;
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+
39
+ // src/utils/validators/validators.ts
40
+ import { Type } from "@sinclair/typebox";
41
+ import { TypeCompiler } from "@sinclair/typebox/compiler";
42
+ var compilerCache = /* @__PURE__ */ new WeakMap();
43
+ function getCompiledValidator(schema) {
44
+ let compiler = compilerCache.get(schema);
45
+ if (!compiler) {
46
+ compiler = TypeCompiler.Compile(schema);
47
+ compilerCache.set(schema, compiler);
48
+ }
49
+ return compiler;
50
+ }
51
+ function validateSchemaOrThrow(schema, data, context) {
52
+ const compiler = getCompiledValidator(schema);
53
+ if (!compiler.Check(data)) {
54
+ throw new Error(`${context}\u9A8C\u8BC1\u5931\u8D25`);
55
+ }
56
+ return data;
57
+ }
58
+ function validateAllSchemas(config, data) {
59
+ if (config.body) {
60
+ validateSchemaOrThrow(config.body, data.body, "\u8BF7\u6C42\u4F53");
61
+ }
62
+ if (config.query) {
63
+ validateSchemaOrThrow(config.query, data.query, "Query\u53C2\u6570");
64
+ }
65
+ if (config.params) {
66
+ validateSchemaOrThrow(config.params, data.params, "\u8DEF\u5F84\u53C2\u6570");
67
+ }
68
+ if (config.headers) {
69
+ validateSchemaOrThrow(config.headers, data.headers, "\u8BF7\u6C42\u5934");
70
+ }
71
+ if (config.cookies) {
72
+ validateSchemaOrThrow(config.cookies, data.cookies, "Cookie");
73
+ }
74
+ return data;
75
+ }
76
+ function precompileSchemas(config) {
77
+ if (config.body) getCompiledValidator(config.body);
78
+ if (config.query) getCompiledValidator(config.query);
79
+ if (config.params) getCompiledValidator(config.params);
80
+ if (config.headers) getCompiledValidator(config.headers);
81
+ if (config.cookies) getCompiledValidator(config.cookies);
82
+ }
83
+
84
+ // src/utils/sse.ts
85
+ function formatSSEEvent(event) {
86
+ const lines = [];
87
+ if (event.id !== void 0) {
88
+ lines.push(`id: ${event.id}`);
89
+ }
90
+ if (event.event !== void 0) {
91
+ lines.push(`event: ${event.event}`);
92
+ }
93
+ if (event.retry !== void 0) {
94
+ lines.push(`retry: ${event.retry}`);
95
+ }
96
+ const dataStr = typeof event.data === "string" ? event.data : JSON.stringify(event.data);
97
+ const dataLines = dataStr.split("\n");
98
+ for (const line of dataLines) {
99
+ lines.push(`data: ${line}`);
100
+ }
101
+ return lines.join("\n") + "\n\n";
102
+ }
103
+ function createSSEHandler(schemaOrGenerator, maybeGenerator) {
104
+ const hasSchema = typeof schemaOrGenerator !== "function";
105
+ const schema = hasSchema ? schemaOrGenerator : {};
106
+ const generator = hasSchema ? maybeGenerator : schemaOrGenerator;
107
+ if (schema.body || schema.query || schema.params || schema.headers || schema.cookies) {
108
+ precompileSchemas(schema);
109
+ }
110
+ const handlerFn = async (req) => {
111
+ try {
112
+ const query = parseQuery(req);
113
+ const headers = parseHeaders(req);
114
+ const cookies = parseCookies(req);
115
+ const params = req.params || {};
116
+ const data = { body: void 0, query, params, headers, cookies };
117
+ if (schema.body || schema.query || schema.params || schema.headers || schema.cookies) {
118
+ validateAllSchemas(schema, data);
119
+ }
120
+ const stream = new ReadableStream({
121
+ async start(controller) {
122
+ const encoder = new TextEncoder();
123
+ try {
124
+ const gen = generator({
125
+ req,
126
+ body: void 0,
127
+ query,
128
+ params,
129
+ headers,
130
+ cookies
131
+ });
132
+ for await (const event of gen) {
133
+ const formatted = formatSSEEvent(event);
134
+ controller.enqueue(encoder.encode(formatted));
135
+ }
136
+ } catch (error) {
137
+ const errorEvent = formatSSEEvent({
138
+ event: "error",
139
+ data: {
140
+ message: error instanceof Error ? error.message : "Unknown error"
141
+ }
142
+ });
143
+ controller.enqueue(encoder.encode(errorEvent));
144
+ } finally {
145
+ controller.close();
146
+ }
147
+ }
148
+ });
149
+ return new Response(stream, {
150
+ headers: {
151
+ "Content-Type": "text/event-stream",
152
+ "Cache-Control": "no-cache",
153
+ "Connection": "keep-alive",
154
+ "X-Accel-Buffering": "no"
155
+ // Nginx 禁用缓冲
156
+ }
157
+ });
158
+ } catch (error) {
159
+ return new Response(
160
+ JSON.stringify({
161
+ success: false,
162
+ error: "Validation Error",
163
+ message: error instanceof Error ? error.message : "Unknown error"
164
+ }),
165
+ {
166
+ status: 400,
167
+ headers: { "Content-Type": "application/json" }
168
+ }
169
+ );
170
+ }
171
+ };
172
+ const handler = handlerFn;
173
+ handler.__sse = { __brand: "SSE" };
174
+ handler.__schema = schema;
175
+ handler.__returnType = void 0;
176
+ return handler;
177
+ }
178
+ export {
179
+ createSSEHandler
180
+ };
181
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/parsers.ts","../../src/utils/validators/validators.ts","../../src/utils/sse.ts"],"sourcesContent":["// src/parsers.ts\nimport qs from \"qs\";\nimport cookie from \"cookie\";\n\n// 文件信息接口\nexport interface FileInfo {\n name: string;\n type: string;\n size: number;\n data: ArrayBuffer;\n}\n\n// 表单数据接口\nexport interface FormData {\n fields: Record<string, string>;\n files: Record<string, FileInfo>;\n}\n\n/**\n * 简化的请求体解析函数\n * 优先简洁性,处理最常见的场景\n */\nexport async function parseBody(req: Request): Promise<unknown> {\n const contentType = req.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"application/json\")) {\n return await req.json();\n }\n if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n const text = await req.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n return await req.text(); // fallback\n}\n\n/**\n * 解析 multipart/form-data 格式\n * 支持文件上传和普通表单字段\n */\nasync function parseMultipartFormData(req: Request): Promise<FormData> {\n const formData = await req.formData();\n const result: FormData = {\n fields: {},\n files: {},\n };\n\n for (const [key, value] of formData.entries()) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"name\" in value &&\n \"type\" in value &&\n \"size\" in value\n ) {\n // 处理文件\n const file = value as any;\n const arrayBuffer = await file.arrayBuffer();\n result.files[key] = {\n name: file.name,\n type: file.type,\n size: file.size,\n data: arrayBuffer,\n };\n } else {\n // 处理普通字段\n result.fields[key] = value as string;\n }\n }\n\n return result;\n}\n\n/**\n * 解析请求体为特定类型\n * 提供类型安全的解析方法\n */\nexport async function parseBodyAs<T>(req: Request): Promise<T> {\n const body = await parseBody(req);\n return body as T;\n}\n\n/**\n * 解析请求体为表单数据\n * 专门用于处理 multipart/form-data\n */\nexport async function parseFormData(req: Request): Promise<FormData> {\n const contentType = req.headers.get(\"content-type\") || \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n throw new Error(\"请求不是 multipart/form-data 格式\");\n }\n\n return await parseMultipartFormData(req);\n}\n\n/**\n * 解析请求体为文件\n * 专门用于处理文件上传\n */\nexport async function parseFile(req: Request): Promise<FileInfo> {\n const contentType = req.headers.get(\"content-type\") || \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n throw new Error(\"请求不是 multipart/form-data 格式\");\n }\n\n const formData = await parseMultipartFormData(req);\n const fileKeys = Object.keys(formData.files);\n\n if (fileKeys.length === 0) {\n throw new Error(\"请求中没有文件\");\n }\n\n if (fileKeys.length > 1) {\n throw new Error(\"请求中包含多个文件,请使用 parseFormData\");\n }\n\n return formData.files[fileKeys[0]];\n}\n\n/**\n * 快速提取 query string(避免创建 URL 对象)\n */\nfunction extractQueryString(url: string): string {\n const qIndex = url.indexOf(\"?\");\n if (qIndex === -1) return \"\";\n\n const hashIndex = url.indexOf(\"#\", qIndex);\n return hashIndex === -1\n ? url.substring(qIndex + 1)\n : url.substring(qIndex + 1, hashIndex);\n}\n\n/** 获取查询字符串,直接返回对象 */\nexport function parseQuery(req: Request): Record<string, unknown> {\n const queryString = extractQueryString(req.url);\n if (!queryString) return {};\n return qs.parse(queryString);\n}\n\n/**\n * 快速解析简单查询字符串(不支持嵌套,但更快)\n * 适用于简单的 key=value&key2=value2 场景\n */\nexport function parseQueryFast(req: Request): Record<string, string> {\n const queryString = extractQueryString(req.url);\n if (!queryString) return {};\n\n const result: Record<string, string> = Object.create(null);\n const pairs = queryString.split(\"&\");\n\n for (const pair of pairs) {\n const eqIndex = pair.indexOf(\"=\");\n if (eqIndex === -1) {\n result[decodeURIComponent(pair)] = \"\";\n } else {\n const key = decodeURIComponent(pair.substring(0, eqIndex));\n const value = decodeURIComponent(pair.substring(eqIndex + 1));\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/** 解析请求头,返回对象 */\nexport function parseHeaders(req: Request): Record<string, string> {\n const headers: Record<string, string> = Object.create(null);\n req.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\n/**\n * 获取单个请求头(避免解析全部)\n */\nexport function getHeader(req: Request, name: string): string | null {\n return req.headers.get(name);\n}\n\n/** 使用cookie库解析Cookie,保证可靠性 */\nexport function parseCookies(req: Request): Record<string, string> {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return {};\n\n try {\n const parsed = cookie.parse(cookieHeader);\n // 过滤掉undefined和null值\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n }\n return result;\n } catch {\n return {};\n }\n}\n\n/**\n * 快速解析 Cookie(简化版,不使用外部库)\n * 适用于简单的 cookie 场景\n */\nexport function parseCookiesFast(req: Request): Record<string, string> {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return {};\n\n const result: Record<string, string> = Object.create(null);\n const pairs = cookieHeader.split(\";\");\n\n for (const pair of pairs) {\n const trimmed = pair.trim();\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex > 0) {\n const key = trimmed.substring(0, eqIndex).trim();\n const value = trimmed.substring(eqIndex + 1).trim();\n // 移除引号\n result[key] =\n value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value;\n }\n }\n\n return result;\n}\n\n/**\n * 获取单个 Cookie 值(避免解析全部)\n */\nexport function getCookie(req: Request, name: string): string | null {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return null;\n\n const prefix = `${name}=`;\n const pairs = cookieHeader.split(\";\");\n\n for (const pair of pairs) {\n const trimmed = pair.trim();\n if (trimmed.startsWith(prefix)) {\n const value = trimmed.substring(prefix.length).trim();\n return value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value;\n }\n }\n\n return null;\n}\n","/**\n * Schema 验证器 - 简洁版\n *\n * 特点:\n * - WeakMap 缓存避免内存泄漏\n * - TypeCompiler JIT 编译,性能最佳\n * - 支持 FormatRegistry(需确保同一实例)\n *\n * @version 7.0.0\n */\n\nimport { Type } from \"@sinclair/typebox\";\nimport type { Static, TSchema } from \"@sinclair/typebox\";\nimport { TypeCompiler, type TypeCheck } from \"@sinclair/typebox/compiler\";\nimport { Value } from \"@sinclair/typebox/value\";\n\n// ============== 类型定义 ==============\n\n/** Schema 配置接口 */\nexport interface SchemaConfig {\n body?: TSchema;\n query?: TSchema;\n params?: TSchema;\n headers?: TSchema;\n cookies?: TSchema;\n}\n\n/** 验证错误接口 */\nexport interface ValidationError {\n path: string;\n message: string;\n code: string;\n value?: unknown;\n}\n\n/** 验证结果 */\nexport type ValidationResult<T = unknown> =\n | { success: true; data: T }\n | { success: false; errors: ValidationError[] };\n\n// ============== 缓存 ==============\n\n/** 编译器缓存 - WeakMap 避免内存泄漏 */\nconst compilerCache = new WeakMap<TSchema, TypeCheck<TSchema>>();\n\n// ============== 核心函数 ==============\n\n/**\n * 获取或创建编译后的验证器\n */\nfunction getCompiledValidator<T extends TSchema>(schema: T): TypeCheck<T> {\n let compiler = compilerCache.get(schema);\n if (!compiler) {\n compiler = TypeCompiler.Compile(schema);\n compilerCache.set(schema, compiler);\n }\n return compiler as TypeCheck<T>;\n}\n\n/**\n * 验证单个 Schema(返回结果对象)\n */\nexport function validateSchema<T extends TSchema>(\n schema: T,\n data: unknown,\n): ValidationResult<Static<T>> {\n try {\n const compiler = getCompiledValidator(schema);\n\n if (compiler.Check(data)) {\n return { success: true, data: data as Static<T> };\n }\n\n // 收集错误\n const errors: ValidationError[] = [];\n for (const error of compiler.Errors(data)) {\n errors.push({\n path: error.path,\n message: error.message,\n code: \"VALIDATION_FAILED\",\n value: error.value,\n });\n }\n return { success: false, errors };\n } catch (error) {\n return {\n success: false,\n errors: [\n {\n path: \"\",\n message: error instanceof Error ? error.message : \"验证异常\",\n code: \"VALIDATION_EXCEPTION\",\n },\n ],\n };\n }\n}\n\n/**\n * 验证 Schema(抛出异常版本,用于框架内部)\n */\nexport function validateSchemaOrThrow<T extends TSchema>(\n schema: T,\n data: unknown,\n context: string,\n): Static<T> {\n const compiler = getCompiledValidator(schema);\n\n if (!compiler.Check(data)) {\n throw new Error(`${context}验证失败`);\n }\n\n return data as Static<T>;\n}\n\n/**\n * 快速验证(只返回布尔值)\n */\nexport function validateFast<T extends TSchema>(\n schema: T,\n data: unknown,\n): data is Static<T> {\n const compiler = getCompiledValidator(schema);\n return compiler.Check(data);\n}\n\n/**\n * 批量验证所有 Schema(用于请求验证)\n */\nexport function validateAllSchemas(\n config: SchemaConfig,\n data: {\n body: unknown;\n query: unknown;\n params: unknown;\n headers: unknown;\n cookies: unknown;\n },\n): typeof data {\n if (config.body) {\n validateSchemaOrThrow(config.body, data.body, \"请求体\");\n }\n if (config.query) {\n validateSchemaOrThrow(config.query, data.query, \"Query参数\");\n }\n if (config.params) {\n validateSchemaOrThrow(config.params, data.params, \"路径参数\");\n }\n if (config.headers) {\n validateSchemaOrThrow(config.headers, data.headers, \"请求头\");\n }\n if (config.cookies) {\n validateSchemaOrThrow(config.cookies, data.cookies, \"Cookie\");\n }\n return data;\n}\n\n/**\n * 预编译 Schema(启动时调用,避免首次请求开销)\n */\nexport function precompileSchemas(config: SchemaConfig): void {\n if (config.body) getCompiledValidator(config.body);\n if (config.query) getCompiledValidator(config.query);\n if (config.params) getCompiledValidator(config.params);\n if (config.headers) getCompiledValidator(config.headers);\n if (config.cookies) getCompiledValidator(config.cookies);\n}\n\n/**\n * 创建类型特化的验证器(高频使用场景)\n */\nexport function createValidator<T extends TSchema>(\n schema: T,\n): (data: unknown) => ValidationResult<Static<T>> {\n return (data: unknown) => validateSchema(schema, data);\n}\n\n/**\n * 获取缓存统计(调试用)\n */\nexport function getValidatorCacheStats(): { cacheType: string; note: string } {\n return {\n cacheType: \"WeakMap\",\n note: \"WeakMap 缓存会随 Schema 对象自动清理,无内存泄漏风险\",\n };\n}\n\n// 导出 TypeBox 类型\nexport { Type, Static, TSchema };\n","/**\n * Server-Sent Events (SSE) 支持\n *\n * 用于实现流式响应,如 AI 聊天、实时通知等\n *\n * @example\n * ```typescript\n * import { createSSEHandler, Type } from 'vafast'\n *\n * const streamHandler = createSSEHandler(\n * { query: Type.Object({ prompt: Type.String() }) },\n * async function* ({ query }) {\n * yield { event: 'start', data: { message: 'Starting...' } }\n *\n * for await (const chunk of aiStream(query.prompt)) {\n * yield { data: chunk }\n * }\n *\n * yield { event: 'end', data: { message: 'Done!' } }\n * }\n * )\n * ```\n */\n\nimport type { RouteSchema, HandlerContext } from \"../types/schema\";\nimport { parseQuery, parseHeaders, parseCookies } from \"./parsers\";\nimport { precompileSchemas, validateAllSchemas } from \"./validators/validators\";\n\n/**\n * SSE 事件类型\n */\nexport interface SSEEvent<T = unknown> {\n /** 事件名称(可选,默认为 message) */\n event?: string;\n /** 事件数据 */\n data: T;\n /** 事件 ID(可选) */\n id?: string;\n /** 重试间隔(毫秒,可选) */\n retry?: number;\n}\n\n/**\n * SSE 生成器函数类型\n */\nexport type SSEGenerator<T extends RouteSchema = RouteSchema> = (\n ctx: HandlerContext<T>\n) => AsyncGenerator<SSEEvent<unknown>, void, unknown>;\n\n/**\n * 格式化 SSE 事件为字符串\n */\nfunction formatSSEEvent(event: SSEEvent): string {\n const lines: string[] = [];\n\n if (event.id !== undefined) {\n lines.push(`id: ${event.id}`);\n }\n\n if (event.event !== undefined) {\n lines.push(`event: ${event.event}`);\n }\n\n if (event.retry !== undefined) {\n lines.push(`retry: ${event.retry}`);\n }\n\n // 数据可能是多行的,需要分行处理\n const dataStr = typeof event.data === 'string'\n ? event.data\n : JSON.stringify(event.data);\n\n const dataLines = dataStr.split('\\n');\n for (const line of dataLines) {\n lines.push(`data: ${line}`);\n }\n\n return lines.join('\\n') + '\\n\\n';\n}\n\n/**\n * SSE 标记类型 - 使用字面量品牌类型\n */\nexport type SSEMarker = { readonly __brand: 'SSE' }\n\n/**\n * SSE Handler 类型标记\n */\nexport interface SSEHandler<TSchema extends RouteSchema = RouteSchema> {\n (req: Request): Promise<Response>;\n /** 返回类型标记 - SSE 流的数据类型 */\n readonly __returnType: unknown;\n /** Schema 类型标记 */\n readonly __schema: TSchema;\n /** SSE 标记 - 使用品牌类型确保不被扩展 */\n readonly __sse: SSEMarker;\n}\n\n/**\n * 创建 SSE 流式响应处理器\n *\n * @example\n * ```typescript\n * // 基础用法\n * const streamChat = createSSEHandler(\n * { query: Type.Object({ message: Type.String() }) },\n * async function* ({ query }) {\n * const response = await ai.chat(query.message);\n *\n * for await (const chunk of response) {\n * yield { data: { text: chunk } };\n * }\n * }\n * );\n *\n * // 使用路由\n * route('GET', '/chat/stream', streamChat)\n * ```\n */\nexport function createSSEHandler<const T extends RouteSchema>(\n schema: T,\n generator: SSEGenerator<T>\n): SSEHandler<T>;\n\nexport function createSSEHandler(\n generator: SSEGenerator<RouteSchema>\n): SSEHandler<RouteSchema>;\n\nexport function createSSEHandler<const T extends RouteSchema>(\n schemaOrGenerator: T | SSEGenerator<T>,\n maybeGenerator?: SSEGenerator<T>\n): SSEHandler<T> {\n // 判断调用方式\n const hasSchema = typeof schemaOrGenerator !== 'function';\n const schema = hasSchema ? (schemaOrGenerator as T) : ({} as T);\n const generator = hasSchema\n ? maybeGenerator!\n : (schemaOrGenerator as SSEGenerator<T>);\n\n // 预编译 schema\n if (schema.body || schema.query || schema.params || schema.headers || schema.cookies) {\n precompileSchemas(schema);\n }\n\n const handlerFn = async (req: Request): Promise<Response> => {\n try {\n // 解析请求数据\n const query = parseQuery(req);\n const headers = parseHeaders(req);\n const cookies = parseCookies(req);\n const params = ((req as unknown as Record<string, unknown>).params as Record<string, string>) || {};\n\n // 验证 schema\n const data = { body: undefined, query, params, headers, cookies };\n if (schema.body || schema.query || schema.params || schema.headers || schema.cookies) {\n validateAllSchemas(schema, data);\n }\n\n // 创建 SSE 流\n const stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder();\n\n try {\n const gen = generator({\n req,\n body: undefined as HandlerContext<T>['body'],\n query: query as HandlerContext<T>['query'],\n params: params as HandlerContext<T>['params'],\n headers: headers as HandlerContext<T>['headers'],\n cookies: cookies as HandlerContext<T>['cookies'],\n });\n\n for await (const event of gen) {\n const formatted = formatSSEEvent(event);\n controller.enqueue(encoder.encode(formatted));\n }\n } catch (error) {\n // 发送错误事件\n const errorEvent = formatSSEEvent({\n event: 'error',\n data: {\n message: error instanceof Error ? error.message : 'Unknown error'\n }\n });\n controller.enqueue(encoder.encode(errorEvent));\n } finally {\n controller.close();\n }\n }\n });\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no', // Nginx 禁用缓冲\n }\n });\n } catch (error) {\n // 验证错误等\n return new Response(\n JSON.stringify({\n success: false,\n error: 'Validation Error',\n message: error instanceof Error ? error.message : 'Unknown error'\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n }\n );\n }\n };\n\n // 添加类型标记\n const handler = handlerFn as SSEHandler<T>;\n (handler as unknown as { __sse: SSEMarker }).__sse = { __brand: 'SSE' } as const;\n (handler as unknown as { __schema: T }).__schema = schema;\n (handler as unknown as { __returnType: unknown }).__returnType = undefined;\n return handler;\n}\n\n\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vafast",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
4
4
  "description": "极简结构化Web框架,支持 Bun 和 Node.js。Go风格,函数优先。",
5
5
  "type": "module",
6
6
  "repository": {