@faapi/faapi 0.0.0-canary.ede2e84 → 0.0.0-canary.f08a14e
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/dist/cli/index.js +7 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +290 -283
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,17 @@ interface SseEvent {
|
|
|
54
54
|
interface SseWriter {
|
|
55
55
|
/** 推送一个 SSE 事件 */
|
|
56
56
|
send(event: SseEvent): void;
|
|
57
|
+
/**
|
|
58
|
+
* 直接写入原始字节/字符串,不做任何 SSE 序列化
|
|
59
|
+
*
|
|
60
|
+
* 用于透传上游已有的 SSE 原文(如 LLM 中转平台逐 chunk 转发 OpenAI 响应)。
|
|
61
|
+
* 调用方负责保证内容符合 HTML5 SSE 规范;`send` 会再次加 `data: ` 前缀,
|
|
62
|
+
* 不适用于原文透传场景。
|
|
63
|
+
*
|
|
64
|
+
* 接受 string 或 Uint8Array(Buffer 是 Uint8Array 子类,自然兼容)。
|
|
65
|
+
* 与 `send` 一致:close/aborted 后静默忽略,不抛错。
|
|
66
|
+
*/
|
|
67
|
+
sendRaw(chunk: string | Uint8Array): void;
|
|
57
68
|
/** 推送一个 error 事件并关闭流(用于流式输出中报错的优雅终止) */
|
|
58
69
|
sendError(error: unknown): void;
|
|
59
70
|
/** 关闭流(多次调用安全) */
|
|
@@ -1064,6 +1075,30 @@ interface ValidationIssue {
|
|
|
1064
1075
|
}
|
|
1065
1076
|
type ValidationErrorCode = 'TYPE_MISMATCH' | 'MISSING_FIELD' | 'INVALID_FORMAT' | 'INVALID_VALUE' | 'COERCE_FAILED';
|
|
1066
1077
|
|
|
1078
|
+
/**
|
|
1079
|
+
* 从 Request 对象创建 FaapiContext
|
|
1080
|
+
* @param request Web Request 对象
|
|
1081
|
+
* @param params 动态路由参数
|
|
1082
|
+
* @param config 自定义业务配置(来自 faapi.config.ts)
|
|
1083
|
+
* @param ip 客户端 IP(由调用方从 IncomingMessage 提取,HTTP/WS 握手均通过 utils/getClientIp)
|
|
1084
|
+
*/
|
|
1085
|
+
declare function createContext(request: Request, params: Record<string, string>, config?: Record<string, unknown>, ip?: string): FaapiContext;
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* 调用路由 handler 并将返回值转为 Response
|
|
1089
|
+
*
|
|
1090
|
+
* 流程(洋葱模型):
|
|
1091
|
+
* 1. 中间件按洋葱模型执行:mw1.before → mw2.before → ... → handler → ... → mw2.after → mw1.after
|
|
1092
|
+
* 2. 中间件不调用 next() 即拦截请求(必须返回 Response)
|
|
1093
|
+
* 3. 中间件可用 try/catch 捕获内层错误
|
|
1094
|
+
* 4. 最内层执行注入器(按需)→ handler
|
|
1095
|
+
*
|
|
1096
|
+
* 注入器与中间件解耦:
|
|
1097
|
+
* - 注入器按 handler 参数名匹配,只执行需要的
|
|
1098
|
+
* - 注入器可读取中间件塞进 ctx 的值
|
|
1099
|
+
*/
|
|
1100
|
+
declare function invokeHandler(handler: (...args: unknown[]) => unknown, ctx: FaapiContext, body?: unknown, middlewares?: FaapiMiddleware[], injectors?: InjectorMap): Promise<Response>;
|
|
1101
|
+
|
|
1067
1102
|
interface InjectOptions {
|
|
1068
1103
|
method?: string;
|
|
1069
1104
|
path?: string;
|
|
@@ -1157,4 +1192,4 @@ type ProdApp = AppBase;
|
|
|
1157
1192
|
*/
|
|
1158
1193
|
declare function createProdApp(options?: CreateAppOptions): Promise<ProdApp>;
|
|
1159
1194
|
|
|
1160
|
-
export { type ProdApp as App, type CorsOptions, type CreateAppOptions, type DevApp, type FaapiConfig, type FaapiContext, type FaapiContextConfig, FaapiError, type FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, type HelmetOptions, type InjectOptions, type InjectResponse, type Injector, type InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type RouteInfo, type RouteInputSchema, type RouteManifest, RouteNotFoundError, type RouteOutputSchema, type RouteParamSchema, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, type SseEvent, type SseWriter, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, collectRouteSchemaSources, cors, createProdApp as createApp, createDevApp, createProdApp, createProgram, extractTypeInfo, getInputTypeForMethod, helmet, invalidateProgramCache, loadConfig, logger, resolveTypeNode };
|
|
1195
|
+
export { type ProdApp as App, type CorsOptions, type CreateAppOptions, type DevApp, type FaapiConfig, type FaapiContext, type FaapiContextConfig, FaapiError, type FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, type HelmetOptions, type InjectOptions, type InjectResponse, type Injector, type InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type RouteInfo, type RouteInputSchema, type RouteManifest, RouteNotFoundError, type RouteOutputSchema, type RouteParamSchema, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, type SseEvent, type SseWriter, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, collectRouteSchemaSources, cors, createProdApp as createApp, createContext, createDevApp, createProdApp, createProgram, extractTypeInfo, getInputTypeForMethod, helmet, invalidateProgramCache, invokeHandler, loadConfig, logger, resolveTypeNode };
|
package/dist/index.js
CHANGED
|
@@ -1095,182 +1095,6 @@ var ModuleLoadError = class extends FaapiError {
|
|
|
1095
1095
|
}
|
|
1096
1096
|
};
|
|
1097
1097
|
|
|
1098
|
-
// src/cli/createAppCore.ts
|
|
1099
|
-
import fs4 from "fs";
|
|
1100
|
-
import path7 from "path";
|
|
1101
|
-
import { PassThrough } from "stream";
|
|
1102
|
-
|
|
1103
|
-
// src/router/sortRoutes.ts
|
|
1104
|
-
function sortRoutes(routes) {
|
|
1105
|
-
return [...routes].sort((a, b) => {
|
|
1106
|
-
if (a.isDynamic !== b.isDynamic) {
|
|
1107
|
-
return a.isDynamic ? 1 : -1;
|
|
1108
|
-
}
|
|
1109
|
-
if (a.isCatchAll !== b.isCatchAll) {
|
|
1110
|
-
return a.isCatchAll ? 1 : -1;
|
|
1111
|
-
}
|
|
1112
|
-
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1113
|
-
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1114
|
-
if (aSegments !== bSegments) {
|
|
1115
|
-
return aSegments - bSegments;
|
|
1116
|
-
}
|
|
1117
|
-
return a.urlPath.localeCompare(b.urlPath);
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// src/router/detectRouteConflicts.ts
|
|
1122
|
-
function detectRouteConflicts(routes) {
|
|
1123
|
-
const map = /* @__PURE__ */ new Map();
|
|
1124
|
-
for (const route of routes) {
|
|
1125
|
-
const key = `${route.method} ${route.urlPath}`;
|
|
1126
|
-
const existing = map.get(key);
|
|
1127
|
-
if (existing) {
|
|
1128
|
-
existing.files.push(route.filePath);
|
|
1129
|
-
} else {
|
|
1130
|
-
map.set(key, {
|
|
1131
|
-
method: route.method,
|
|
1132
|
-
urlPath: route.urlPath,
|
|
1133
|
-
files: [route.filePath]
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
const conflicts = [];
|
|
1138
|
-
for (const conflict of map.values()) {
|
|
1139
|
-
if (conflict.files.length > 1) {
|
|
1140
|
-
conflicts.push(conflict);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
return conflicts;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
// src/server/createServer.ts
|
|
1147
|
-
import {
|
|
1148
|
-
createServer as createHttpServer
|
|
1149
|
-
} from "http";
|
|
1150
|
-
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
1151
|
-
import { readFileSync } from "fs";
|
|
1152
|
-
import { Readable as Readable2 } from "stream";
|
|
1153
|
-
import path5 from "path";
|
|
1154
|
-
|
|
1155
|
-
// src/router/matchRoute.ts
|
|
1156
|
-
function matchRoute(routes, method, path9) {
|
|
1157
|
-
for (const route of routes) {
|
|
1158
|
-
if (route.method !== method) {
|
|
1159
|
-
continue;
|
|
1160
|
-
}
|
|
1161
|
-
if (!route.isDynamic) {
|
|
1162
|
-
if (route.urlPath === path9) {
|
|
1163
|
-
return { route, params: {} };
|
|
1164
|
-
}
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
|
|
1168
|
-
if (params !== null) {
|
|
1169
|
-
return { route, params };
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
return null;
|
|
1173
|
-
}
|
|
1174
|
-
function matchWsRoute(wsRoutes, path9) {
|
|
1175
|
-
for (const route of wsRoutes) {
|
|
1176
|
-
if (!route.isDynamic) {
|
|
1177
|
-
if (route.urlPath === path9) {
|
|
1178
|
-
return { route, params: {} };
|
|
1179
|
-
}
|
|
1180
|
-
continue;
|
|
1181
|
-
}
|
|
1182
|
-
const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
|
|
1183
|
-
if (params !== null) {
|
|
1184
|
-
return { route, params };
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return null;
|
|
1188
|
-
}
|
|
1189
|
-
function matchDynamicPath(pattern, path9, paramNames, isCatchAll) {
|
|
1190
|
-
const patternSegments = pattern.split("/").filter(Boolean);
|
|
1191
|
-
const pathSegments = path9.split("/").filter(Boolean);
|
|
1192
|
-
if (isCatchAll) {
|
|
1193
|
-
const nonCatchAllCount = patternSegments.length - 1;
|
|
1194
|
-
if (pathSegments.length <= nonCatchAllCount) {
|
|
1195
|
-
return null;
|
|
1196
|
-
}
|
|
1197
|
-
const params2 = {};
|
|
1198
|
-
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
1199
|
-
const patternSeg = patternSegments[i];
|
|
1200
|
-
const pathSeg = pathSegments[i];
|
|
1201
|
-
if (patternSeg.startsWith(":")) {
|
|
1202
|
-
const paramName = patternSeg.slice(1);
|
|
1203
|
-
params2[paramName] = pathSeg;
|
|
1204
|
-
} else if (patternSeg !== pathSeg) {
|
|
1205
|
-
return null;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
1209
|
-
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
1210
|
-
params2[catchAllParamName] = catchAllValue;
|
|
1211
|
-
if (Object.keys(params2).length !== paramNames.length) {
|
|
1212
|
-
return null;
|
|
1213
|
-
}
|
|
1214
|
-
return params2;
|
|
1215
|
-
}
|
|
1216
|
-
if (patternSegments.length !== pathSegments.length) {
|
|
1217
|
-
return null;
|
|
1218
|
-
}
|
|
1219
|
-
const params = {};
|
|
1220
|
-
for (let i = 0; i < patternSegments.length; i++) {
|
|
1221
|
-
const patternSeg = patternSegments[i];
|
|
1222
|
-
const pathSeg = pathSegments[i];
|
|
1223
|
-
if (patternSeg.startsWith(":")) {
|
|
1224
|
-
const paramName = patternSeg.slice(1);
|
|
1225
|
-
params[paramName] = pathSeg;
|
|
1226
|
-
} else if (patternSeg !== pathSeg) {
|
|
1227
|
-
return null;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
if (Object.keys(params).length !== paramNames.length) {
|
|
1231
|
-
return null;
|
|
1232
|
-
}
|
|
1233
|
-
return params;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// src/loader/resolveExports.ts
|
|
1237
|
-
function resolveExport(module, exportName) {
|
|
1238
|
-
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
1239
|
-
return module[exportName];
|
|
1240
|
-
}
|
|
1241
|
-
const defaultExport = module.default;
|
|
1242
|
-
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
1243
|
-
const value = defaultExport[exportName];
|
|
1244
|
-
if (value !== void 0) {
|
|
1245
|
-
return value;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return void 0;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// src/loader/validateRouteModule.ts
|
|
1252
|
-
function validateRouteModule(value, method, filePath) {
|
|
1253
|
-
if (typeof value !== "function") {
|
|
1254
|
-
throw new Error(
|
|
1255
|
-
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
1256
|
-
);
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// src/loader/loadRouteModule.ts
|
|
1261
|
-
async function loadRouteModule(filePath, method) {
|
|
1262
|
-
let module;
|
|
1263
|
-
try {
|
|
1264
|
-
module = await importWithCacheBust(filePath);
|
|
1265
|
-
} catch (err) {
|
|
1266
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1267
|
-
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
1268
|
-
}
|
|
1269
|
-
const handler = resolveExport(module, method);
|
|
1270
|
-
validateRouteModule(handler, method, filePath);
|
|
1271
|
-
return { handler, method };
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
1098
|
// src/runtime/sse.ts
|
|
1275
1099
|
function encodeSseEvent(event) {
|
|
1276
1100
|
let out = "";
|
|
@@ -1337,6 +1161,11 @@ function createSseWriter() {
|
|
|
1337
1161
|
const text = encodeSseEvent(event);
|
|
1338
1162
|
controller.enqueue(encoder.encode(text));
|
|
1339
1163
|
},
|
|
1164
|
+
sendRaw(chunk) {
|
|
1165
|
+
if (closed || !controller) return;
|
|
1166
|
+
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
1167
|
+
controller.enqueue(bytes);
|
|
1168
|
+
},
|
|
1340
1169
|
sendError(error) {
|
|
1341
1170
|
if (closed || !controller) return;
|
|
1342
1171
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1475,119 +1304,30 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
1475
1304
|
return ctx;
|
|
1476
1305
|
}
|
|
1477
1306
|
|
|
1478
|
-
// src/utils/
|
|
1479
|
-
function
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
result[key] = value;
|
|
1307
|
+
// src/utils/isPlainObject.ts
|
|
1308
|
+
function isPlainObject(value) {
|
|
1309
|
+
if (value === null || typeof value !== "object") {
|
|
1310
|
+
return false;
|
|
1483
1311
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
// src/utils/parseJsonBody.ts
|
|
1488
|
-
function parseJsonBody(text) {
|
|
1489
|
-
try {
|
|
1490
|
-
const data = JSON.parse(text);
|
|
1491
|
-
return { success: true, data };
|
|
1492
|
-
} catch {
|
|
1493
|
-
return { success: false, error: "Invalid JSON body" };
|
|
1312
|
+
if (Array.isArray(value)) {
|
|
1313
|
+
return false;
|
|
1494
1314
|
}
|
|
1315
|
+
const proto = Object.getPrototypeOf(value);
|
|
1316
|
+
return proto === null || proto === Object.prototype;
|
|
1495
1317
|
}
|
|
1496
1318
|
|
|
1497
|
-
// src/
|
|
1498
|
-
async function
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
const files = [];
|
|
1502
|
-
for (const [key, value] of formData.entries()) {
|
|
1503
|
-
if (value instanceof File) {
|
|
1504
|
-
files.push({
|
|
1505
|
-
name: key,
|
|
1506
|
-
filename: value.name,
|
|
1507
|
-
type: value.type,
|
|
1508
|
-
size: value.size,
|
|
1509
|
-
arrayBuffer: () => value.arrayBuffer()
|
|
1510
|
-
});
|
|
1511
|
-
} else {
|
|
1512
|
-
if (key in fields) {
|
|
1513
|
-
const existing = fields[key];
|
|
1514
|
-
if (Array.isArray(existing)) {
|
|
1515
|
-
existing.push(value);
|
|
1516
|
-
} else {
|
|
1517
|
-
fields[key] = [existing, value];
|
|
1518
|
-
}
|
|
1519
|
-
} else {
|
|
1520
|
-
fields[key] = value;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1319
|
+
// src/response/toResponse.ts
|
|
1320
|
+
async function toResponse(value, meta) {
|
|
1321
|
+
if (value instanceof Promise) {
|
|
1322
|
+
return toResponse(await value, meta);
|
|
1523
1323
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
async function resolveInput(method, request) {
|
|
1529
|
-
const inputType = getInputTypeForMethod(method);
|
|
1530
|
-
if (inputType === "body") {
|
|
1531
|
-
const contentType = request.headers.get("content-type") ?? "";
|
|
1532
|
-
if (contentType.includes("multipart/form-data")) {
|
|
1533
|
-
return parseMultipart(request);
|
|
1324
|
+
const applyMeta = (headers2) => {
|
|
1325
|
+
if (!meta) return;
|
|
1326
|
+
for (const [key, val] of Object.entries(meta.headers)) {
|
|
1327
|
+
headers2.set(key, val);
|
|
1534
1328
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (text2.trim() === "") return null;
|
|
1538
|
-
const params = new URLSearchParams(text2);
|
|
1539
|
-
const obj = {};
|
|
1540
|
-
for (const [key, value] of params) {
|
|
1541
|
-
obj[key] = value;
|
|
1542
|
-
}
|
|
1543
|
-
return obj;
|
|
1544
|
-
}
|
|
1545
|
-
const text = await request.text();
|
|
1546
|
-
if (text.trim() === "") {
|
|
1547
|
-
return null;
|
|
1548
|
-
}
|
|
1549
|
-
const result = parseJsonBody(text);
|
|
1550
|
-
if (!result.success) {
|
|
1551
|
-
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
1552
|
-
{
|
|
1553
|
-
path: "body",
|
|
1554
|
-
code: "INVALID_FORMAT",
|
|
1555
|
-
expected: "JSON",
|
|
1556
|
-
received: "text",
|
|
1557
|
-
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
1558
|
-
}
|
|
1559
|
-
]);
|
|
1560
|
-
}
|
|
1561
|
-
return result.data;
|
|
1562
|
-
}
|
|
1563
|
-
const url = new URL(request.url);
|
|
1564
|
-
return queryToObject(url.searchParams);
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
// src/utils/isPlainObject.ts
|
|
1568
|
-
function isPlainObject(value) {
|
|
1569
|
-
if (value === null || typeof value !== "object") {
|
|
1570
|
-
return false;
|
|
1571
|
-
}
|
|
1572
|
-
if (Array.isArray(value)) {
|
|
1573
|
-
return false;
|
|
1574
|
-
}
|
|
1575
|
-
const proto = Object.getPrototypeOf(value);
|
|
1576
|
-
return proto === null || proto === Object.prototype;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
// src/response/toResponse.ts
|
|
1580
|
-
async function toResponse(value, meta) {
|
|
1581
|
-
if (value instanceof Promise) {
|
|
1582
|
-
return toResponse(await value, meta);
|
|
1583
|
-
}
|
|
1584
|
-
const applyMeta = (headers2) => {
|
|
1585
|
-
if (!meta) return;
|
|
1586
|
-
for (const [key, val] of Object.entries(meta.headers)) {
|
|
1587
|
-
headers2.set(key, val);
|
|
1588
|
-
}
|
|
1589
|
-
for (const cookie of meta.setCookies ?? []) {
|
|
1590
|
-
headers2.append("set-cookie", cookie);
|
|
1329
|
+
for (const cookie of meta.setCookies ?? []) {
|
|
1330
|
+
headers2.append("set-cookie", cookie);
|
|
1591
1331
|
}
|
|
1592
1332
|
};
|
|
1593
1333
|
if (value instanceof Response) {
|
|
@@ -1665,6 +1405,15 @@ async function toResponse(value, meta) {
|
|
|
1665
1405
|
});
|
|
1666
1406
|
}
|
|
1667
1407
|
|
|
1408
|
+
// src/utils/queryToObject.ts
|
|
1409
|
+
function queryToObject(params) {
|
|
1410
|
+
const result = {};
|
|
1411
|
+
for (const [key, value] of params) {
|
|
1412
|
+
result[key] = value;
|
|
1413
|
+
}
|
|
1414
|
+
return result;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1668
1417
|
// src/injection/injectParams.ts
|
|
1669
1418
|
function getBuiltinInjectionValue(type, ctx, body) {
|
|
1670
1419
|
switch (type) {
|
|
@@ -1804,6 +1553,262 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
1804
1553
|
return await compose(middlewares, ctx, finalHandler);
|
|
1805
1554
|
}
|
|
1806
1555
|
|
|
1556
|
+
// src/cli/createAppCore.ts
|
|
1557
|
+
import fs4 from "fs";
|
|
1558
|
+
import path7 from "path";
|
|
1559
|
+
import { PassThrough } from "stream";
|
|
1560
|
+
|
|
1561
|
+
// src/router/sortRoutes.ts
|
|
1562
|
+
function sortRoutes(routes) {
|
|
1563
|
+
return [...routes].sort((a, b) => {
|
|
1564
|
+
if (a.isDynamic !== b.isDynamic) {
|
|
1565
|
+
return a.isDynamic ? 1 : -1;
|
|
1566
|
+
}
|
|
1567
|
+
if (a.isCatchAll !== b.isCatchAll) {
|
|
1568
|
+
return a.isCatchAll ? 1 : -1;
|
|
1569
|
+
}
|
|
1570
|
+
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1571
|
+
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1572
|
+
if (aSegments !== bSegments) {
|
|
1573
|
+
return aSegments - bSegments;
|
|
1574
|
+
}
|
|
1575
|
+
return a.urlPath.localeCompare(b.urlPath);
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// src/router/detectRouteConflicts.ts
|
|
1580
|
+
function detectRouteConflicts(routes) {
|
|
1581
|
+
const map = /* @__PURE__ */ new Map();
|
|
1582
|
+
for (const route of routes) {
|
|
1583
|
+
const key = `${route.method} ${route.urlPath}`;
|
|
1584
|
+
const existing = map.get(key);
|
|
1585
|
+
if (existing) {
|
|
1586
|
+
existing.files.push(route.filePath);
|
|
1587
|
+
} else {
|
|
1588
|
+
map.set(key, {
|
|
1589
|
+
method: route.method,
|
|
1590
|
+
urlPath: route.urlPath,
|
|
1591
|
+
files: [route.filePath]
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
const conflicts = [];
|
|
1596
|
+
for (const conflict of map.values()) {
|
|
1597
|
+
if (conflict.files.length > 1) {
|
|
1598
|
+
conflicts.push(conflict);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
return conflicts;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// src/server/createServer.ts
|
|
1605
|
+
import {
|
|
1606
|
+
createServer as createHttpServer
|
|
1607
|
+
} from "http";
|
|
1608
|
+
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
1609
|
+
import { readFileSync } from "fs";
|
|
1610
|
+
import { Readable as Readable2 } from "stream";
|
|
1611
|
+
import path5 from "path";
|
|
1612
|
+
|
|
1613
|
+
// src/router/matchRoute.ts
|
|
1614
|
+
function matchRoute(routes, method, path9) {
|
|
1615
|
+
for (const route of routes) {
|
|
1616
|
+
if (route.method !== method) {
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
if (!route.isDynamic) {
|
|
1620
|
+
if (route.urlPath === path9) {
|
|
1621
|
+
return { route, params: {} };
|
|
1622
|
+
}
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
|
|
1626
|
+
if (params !== null) {
|
|
1627
|
+
return { route, params };
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
function matchWsRoute(wsRoutes, path9) {
|
|
1633
|
+
for (const route of wsRoutes) {
|
|
1634
|
+
if (!route.isDynamic) {
|
|
1635
|
+
if (route.urlPath === path9) {
|
|
1636
|
+
return { route, params: {} };
|
|
1637
|
+
}
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
|
|
1641
|
+
if (params !== null) {
|
|
1642
|
+
return { route, params };
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return null;
|
|
1646
|
+
}
|
|
1647
|
+
function matchDynamicPath(pattern, path9, paramNames, isCatchAll) {
|
|
1648
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
1649
|
+
const pathSegments = path9.split("/").filter(Boolean);
|
|
1650
|
+
if (isCatchAll) {
|
|
1651
|
+
const nonCatchAllCount = patternSegments.length - 1;
|
|
1652
|
+
if (pathSegments.length <= nonCatchAllCount) {
|
|
1653
|
+
return null;
|
|
1654
|
+
}
|
|
1655
|
+
const params2 = {};
|
|
1656
|
+
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
1657
|
+
const patternSeg = patternSegments[i];
|
|
1658
|
+
const pathSeg = pathSegments[i];
|
|
1659
|
+
if (patternSeg.startsWith(":")) {
|
|
1660
|
+
const paramName = patternSeg.slice(1);
|
|
1661
|
+
params2[paramName] = pathSeg;
|
|
1662
|
+
} else if (patternSeg !== pathSeg) {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
1667
|
+
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
1668
|
+
params2[catchAllParamName] = catchAllValue;
|
|
1669
|
+
if (Object.keys(params2).length !== paramNames.length) {
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
return params2;
|
|
1673
|
+
}
|
|
1674
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
const params = {};
|
|
1678
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
1679
|
+
const patternSeg = patternSegments[i];
|
|
1680
|
+
const pathSeg = pathSegments[i];
|
|
1681
|
+
if (patternSeg.startsWith(":")) {
|
|
1682
|
+
const paramName = patternSeg.slice(1);
|
|
1683
|
+
params[paramName] = pathSeg;
|
|
1684
|
+
} else if (patternSeg !== pathSeg) {
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (Object.keys(params).length !== paramNames.length) {
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1691
|
+
return params;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/loader/resolveExports.ts
|
|
1695
|
+
function resolveExport(module, exportName) {
|
|
1696
|
+
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
1697
|
+
return module[exportName];
|
|
1698
|
+
}
|
|
1699
|
+
const defaultExport = module.default;
|
|
1700
|
+
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
1701
|
+
const value = defaultExport[exportName];
|
|
1702
|
+
if (value !== void 0) {
|
|
1703
|
+
return value;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
return void 0;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/loader/validateRouteModule.ts
|
|
1710
|
+
function validateRouteModule(value, method, filePath) {
|
|
1711
|
+
if (typeof value !== "function") {
|
|
1712
|
+
throw new Error(
|
|
1713
|
+
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/loader/loadRouteModule.ts
|
|
1719
|
+
async function loadRouteModule(filePath, method) {
|
|
1720
|
+
let module;
|
|
1721
|
+
try {
|
|
1722
|
+
module = await importWithCacheBust(filePath);
|
|
1723
|
+
} catch (err) {
|
|
1724
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1725
|
+
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
1726
|
+
}
|
|
1727
|
+
const handler = resolveExport(module, method);
|
|
1728
|
+
validateRouteModule(handler, method, filePath);
|
|
1729
|
+
return { handler, method };
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/utils/parseJsonBody.ts
|
|
1733
|
+
function parseJsonBody(text) {
|
|
1734
|
+
try {
|
|
1735
|
+
const data = JSON.parse(text);
|
|
1736
|
+
return { success: true, data };
|
|
1737
|
+
} catch {
|
|
1738
|
+
return { success: false, error: "Invalid JSON body" };
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/utils/parseMultipart.ts
|
|
1743
|
+
async function parseMultipart(request) {
|
|
1744
|
+
const formData = await request.formData();
|
|
1745
|
+
const fields = {};
|
|
1746
|
+
const files = [];
|
|
1747
|
+
for (const [key, value] of formData.entries()) {
|
|
1748
|
+
if (value instanceof File) {
|
|
1749
|
+
files.push({
|
|
1750
|
+
name: key,
|
|
1751
|
+
filename: value.name,
|
|
1752
|
+
type: value.type,
|
|
1753
|
+
size: value.size,
|
|
1754
|
+
arrayBuffer: () => value.arrayBuffer()
|
|
1755
|
+
});
|
|
1756
|
+
} else {
|
|
1757
|
+
if (key in fields) {
|
|
1758
|
+
const existing = fields[key];
|
|
1759
|
+
if (Array.isArray(existing)) {
|
|
1760
|
+
existing.push(value);
|
|
1761
|
+
} else {
|
|
1762
|
+
fields[key] = [existing, value];
|
|
1763
|
+
}
|
|
1764
|
+
} else {
|
|
1765
|
+
fields[key] = value;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return { fields, files };
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// src/runtime/resolveInput.ts
|
|
1773
|
+
async function resolveInput(method, request) {
|
|
1774
|
+
const inputType = getInputTypeForMethod(method);
|
|
1775
|
+
if (inputType === "body") {
|
|
1776
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
1777
|
+
if (contentType.includes("multipart/form-data")) {
|
|
1778
|
+
return parseMultipart(request);
|
|
1779
|
+
}
|
|
1780
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1781
|
+
const text2 = await request.text();
|
|
1782
|
+
if (text2.trim() === "") return null;
|
|
1783
|
+
const params = new URLSearchParams(text2);
|
|
1784
|
+
const obj = {};
|
|
1785
|
+
for (const [key, value] of params) {
|
|
1786
|
+
obj[key] = value;
|
|
1787
|
+
}
|
|
1788
|
+
return obj;
|
|
1789
|
+
}
|
|
1790
|
+
const text = await request.text();
|
|
1791
|
+
if (text.trim() === "") {
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
const result = parseJsonBody(text);
|
|
1795
|
+
if (!result.success) {
|
|
1796
|
+
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
1797
|
+
{
|
|
1798
|
+
path: "body",
|
|
1799
|
+
code: "INVALID_FORMAT",
|
|
1800
|
+
expected: "JSON",
|
|
1801
|
+
received: "text",
|
|
1802
|
+
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
1803
|
+
}
|
|
1804
|
+
]);
|
|
1805
|
+
}
|
|
1806
|
+
return result.data;
|
|
1807
|
+
}
|
|
1808
|
+
const url = new URL(request.url);
|
|
1809
|
+
return queryToObject(url.searchParams);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1807
1812
|
// src/response/sendNodeResponse.ts
|
|
1808
1813
|
import { Readable } from "stream";
|
|
1809
1814
|
async function sendNodeResponse(response, res) {
|
|
@@ -3373,6 +3378,7 @@ export {
|
|
|
3373
3378
|
collectRouteSchemaSources,
|
|
3374
3379
|
cors,
|
|
3375
3380
|
createProdApp as createApp,
|
|
3381
|
+
createContext,
|
|
3376
3382
|
createDevApp,
|
|
3377
3383
|
createProdApp,
|
|
3378
3384
|
createProgram,
|
|
@@ -3380,6 +3386,7 @@ export {
|
|
|
3380
3386
|
getInputTypeForMethod,
|
|
3381
3387
|
helmet,
|
|
3382
3388
|
invalidateProgramCache,
|
|
3389
|
+
invokeHandler,
|
|
3383
3390
|
loadConfig,
|
|
3384
3391
|
logger,
|
|
3385
3392
|
resolveTypeNode
|