@faapi/faapi 0.0.0-canary.f5b23c6 → 1.0.0-canary.3a00f3e
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 +314 -270
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +69 -11
- package/dist/index.js +554 -465
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1043,6 +1043,87 @@ async function loadConfig(rootDir, dist) {
|
|
|
1043
1043
|
return null;
|
|
1044
1044
|
}
|
|
1045
1045
|
|
|
1046
|
+
// src/cli/loadEnv.ts
|
|
1047
|
+
import fs2 from "fs";
|
|
1048
|
+
import path3 from "path";
|
|
1049
|
+
function resolveEnv() {
|
|
1050
|
+
return process.env.NODE_ENV || "development";
|
|
1051
|
+
}
|
|
1052
|
+
function getEnvFiles(env) {
|
|
1053
|
+
return [".env", ".env.local", `.env.${env}`, `.env.${env}.local`];
|
|
1054
|
+
}
|
|
1055
|
+
function parseEnvFile(content, fileVars) {
|
|
1056
|
+
const result = {};
|
|
1057
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
1058
|
+
for (const line of lines) {
|
|
1059
|
+
const trimmed = line.trim();
|
|
1060
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1061
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
1062
|
+
if (!match) continue;
|
|
1063
|
+
const [, key, rawValue] = match;
|
|
1064
|
+
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
1065
|
+
result[key] = value;
|
|
1066
|
+
}
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
1069
|
+
function parseValue(raw, env) {
|
|
1070
|
+
if (raw === "") return "";
|
|
1071
|
+
if (raw[0] === "'") {
|
|
1072
|
+
const end = raw.indexOf("'", 1);
|
|
1073
|
+
return end === -1 ? raw.slice(1) : raw.slice(1, end);
|
|
1074
|
+
}
|
|
1075
|
+
if (raw[0] === '"') {
|
|
1076
|
+
const match = /^"((?:\\.|[^"\\])*)"/.exec(raw);
|
|
1077
|
+
const inner = match ? match[1] : raw.slice(1);
|
|
1078
|
+
return expandEscapesAndVars(inner, env);
|
|
1079
|
+
}
|
|
1080
|
+
const commentMatch = /^(.*?)(\s+#.*)$/.exec(raw);
|
|
1081
|
+
const value = commentMatch ? commentMatch[1] : raw;
|
|
1082
|
+
return value.trim();
|
|
1083
|
+
}
|
|
1084
|
+
function expandEscapesAndVars(str, env) {
|
|
1085
|
+
const escaped = str.replace(/\\(.)/g, (_, ch) => {
|
|
1086
|
+
switch (ch) {
|
|
1087
|
+
case "n":
|
|
1088
|
+
return "\n";
|
|
1089
|
+
case "r":
|
|
1090
|
+
return "\r";
|
|
1091
|
+
case "t":
|
|
1092
|
+
return " ";
|
|
1093
|
+
case "\\":
|
|
1094
|
+
return "\\";
|
|
1095
|
+
case '"':
|
|
1096
|
+
return '"';
|
|
1097
|
+
default:
|
|
1098
|
+
return ch;
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
return escaped.replace(
|
|
1102
|
+
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
1103
|
+
(_, braced, plain) => {
|
|
1104
|
+
const varName = braced || plain;
|
|
1105
|
+
return env[varName] ?? process.env[varName] ?? "";
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
function loadEnv(rootDir) {
|
|
1110
|
+
const env = resolveEnv();
|
|
1111
|
+
const files = getEnvFiles(env);
|
|
1112
|
+
const merged = {};
|
|
1113
|
+
for (const file of files) {
|
|
1114
|
+
const filePath = path3.join(rootDir, file);
|
|
1115
|
+
if (!fs2.existsSync(filePath)) continue;
|
|
1116
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1117
|
+
const parsed = parseEnvFile(content, merged);
|
|
1118
|
+
Object.assign(merged, parsed);
|
|
1119
|
+
}
|
|
1120
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
1121
|
+
if (process.env[key] === void 0) {
|
|
1122
|
+
process.env[key] = value;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1046
1127
|
// src/errors/FaapiError.ts
|
|
1047
1128
|
var FaapiError = class extends Error {
|
|
1048
1129
|
constructor(code, message, statusCode) {
|
|
@@ -1069,14 +1150,14 @@ var ValidationError = class extends FaapiError {
|
|
|
1069
1150
|
issues;
|
|
1070
1151
|
};
|
|
1071
1152
|
var RouteNotFoundError = class extends FaapiError {
|
|
1072
|
-
constructor(
|
|
1073
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
1153
|
+
constructor(path10) {
|
|
1154
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path10}`, 404);
|
|
1074
1155
|
this.name = "RouteNotFoundError";
|
|
1075
1156
|
}
|
|
1076
1157
|
};
|
|
1077
1158
|
var MethodNotAllowedError = class extends FaapiError {
|
|
1078
|
-
constructor(method,
|
|
1079
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
1159
|
+
constructor(method, path10, allowedMethods) {
|
|
1160
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path10}`, 405);
|
|
1080
1161
|
this.allowedMethods = allowedMethods;
|
|
1081
1162
|
this.name = "MethodNotAllowedError";
|
|
1082
1163
|
}
|
|
@@ -1095,182 +1176,6 @@ var ModuleLoadError = class extends FaapiError {
|
|
|
1095
1176
|
}
|
|
1096
1177
|
};
|
|
1097
1178
|
|
|
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
1179
|
// src/runtime/sse.ts
|
|
1275
1180
|
function encodeSseEvent(event) {
|
|
1276
1181
|
let out = "";
|
|
@@ -1337,6 +1242,11 @@ function createSseWriter() {
|
|
|
1337
1242
|
const text = encodeSseEvent(event);
|
|
1338
1243
|
controller.enqueue(encoder.encode(text));
|
|
1339
1244
|
},
|
|
1245
|
+
sendRaw(chunk) {
|
|
1246
|
+
if (closed || !controller) return;
|
|
1247
|
+
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
1248
|
+
controller.enqueue(bytes);
|
|
1249
|
+
},
|
|
1340
1250
|
sendError(error) {
|
|
1341
1251
|
if (closed || !controller) return;
|
|
1342
1252
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1475,13 +1385,429 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
1475
1385
|
return ctx;
|
|
1476
1386
|
}
|
|
1477
1387
|
|
|
1478
|
-
// src/utils/
|
|
1479
|
-
function
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1388
|
+
// src/utils/isPlainObject.ts
|
|
1389
|
+
function isPlainObject(value) {
|
|
1390
|
+
if (value === null || typeof value !== "object") {
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
if (Array.isArray(value)) {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
const proto = Object.getPrototypeOf(value);
|
|
1397
|
+
return proto === null || proto === Object.prototype;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/response/toResponse.ts
|
|
1401
|
+
async function toResponse(value, meta) {
|
|
1402
|
+
if (value instanceof Promise) {
|
|
1403
|
+
return toResponse(await value, meta);
|
|
1404
|
+
}
|
|
1405
|
+
const applyMeta = (headers2) => {
|
|
1406
|
+
if (!meta) return;
|
|
1407
|
+
for (const [key, val] of Object.entries(meta.headers)) {
|
|
1408
|
+
headers2.set(key, val);
|
|
1409
|
+
}
|
|
1410
|
+
for (const cookie of meta.setCookies ?? []) {
|
|
1411
|
+
headers2.append("set-cookie", cookie);
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
if (value instanceof Response) {
|
|
1415
|
+
if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
|
|
1416
|
+
const headers2 = new Headers(value.headers);
|
|
1417
|
+
applyMeta(headers2);
|
|
1418
|
+
return new Response(value.body, {
|
|
1419
|
+
status: meta.status ?? value.status,
|
|
1420
|
+
headers: headers2
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
return value;
|
|
1424
|
+
}
|
|
1425
|
+
if (value === null || value === void 0) {
|
|
1426
|
+
const status = meta?.status ?? 204;
|
|
1427
|
+
const headers2 = new Headers();
|
|
1428
|
+
applyMeta(headers2);
|
|
1429
|
+
return new Response(null, { status, headers: headers2 });
|
|
1430
|
+
}
|
|
1431
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1432
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1433
|
+
applyMeta(headers2);
|
|
1434
|
+
return new Response(value, {
|
|
1435
|
+
status: meta?.status ?? 200,
|
|
1436
|
+
headers: headers2
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1440
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1441
|
+
applyMeta(headers2);
|
|
1442
|
+
return new Response(value, {
|
|
1443
|
+
status: meta?.status ?? 200,
|
|
1444
|
+
headers: headers2
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
if (value instanceof Uint8Array) {
|
|
1448
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1449
|
+
applyMeta(headers2);
|
|
1450
|
+
return new Response(value, {
|
|
1451
|
+
status: meta?.status ?? 200,
|
|
1452
|
+
headers: headers2
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
1456
|
+
const body2 = JSON.stringify(value);
|
|
1457
|
+
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
1458
|
+
applyMeta(headers2);
|
|
1459
|
+
return new Response(body2, {
|
|
1460
|
+
status: meta?.status ?? 200,
|
|
1461
|
+
headers: headers2
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
if (typeof value === "string") {
|
|
1465
|
+
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1466
|
+
applyMeta(headers2);
|
|
1467
|
+
return new Response(value, {
|
|
1468
|
+
status: meta?.status ?? 200,
|
|
1469
|
+
headers: headers2
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1473
|
+
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1474
|
+
applyMeta(headers2);
|
|
1475
|
+
return new Response(String(value), {
|
|
1476
|
+
status: meta?.status ?? 200,
|
|
1477
|
+
headers: headers2
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
const body = JSON.stringify(value);
|
|
1481
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1482
|
+
applyMeta(headers);
|
|
1483
|
+
return new Response(body, {
|
|
1484
|
+
status: meta?.status ?? 200,
|
|
1485
|
+
headers
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// src/utils/queryToObject.ts
|
|
1490
|
+
function queryToObject(params) {
|
|
1491
|
+
const result = {};
|
|
1492
|
+
for (const [key, value] of params) {
|
|
1493
|
+
result[key] = value;
|
|
1494
|
+
}
|
|
1495
|
+
return result;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// src/injection/injectParams.ts
|
|
1499
|
+
function getBuiltinInjectionValue(type, ctx, body) {
|
|
1500
|
+
switch (type) {
|
|
1501
|
+
case "query":
|
|
1502
|
+
return queryToObject(ctx.query);
|
|
1503
|
+
case "params":
|
|
1504
|
+
return ctx.params;
|
|
1505
|
+
case "headers":
|
|
1506
|
+
return ctx.headers;
|
|
1507
|
+
case "context":
|
|
1508
|
+
return ctx;
|
|
1509
|
+
case "cookies":
|
|
1510
|
+
return ctx.cookies;
|
|
1511
|
+
case "ip":
|
|
1512
|
+
return ctx.ip;
|
|
1513
|
+
case "body":
|
|
1514
|
+
return body;
|
|
1515
|
+
// form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
|
|
1516
|
+
// 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
|
|
1517
|
+
case "form":
|
|
1518
|
+
return body;
|
|
1519
|
+
case "files":
|
|
1520
|
+
if (body && typeof body === "object" && "files" in body) {
|
|
1521
|
+
return body.files;
|
|
1522
|
+
}
|
|
1523
|
+
return [];
|
|
1524
|
+
case "fields":
|
|
1525
|
+
if (body && typeof body === "object" && "fields" in body) {
|
|
1526
|
+
return body.fields;
|
|
1527
|
+
}
|
|
1528
|
+
return {};
|
|
1529
|
+
default:
|
|
1530
|
+
return void 0;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
1534
|
+
const injections = resolveInjection(handler);
|
|
1535
|
+
if (injections.length === 0) {
|
|
1536
|
+
return await handler();
|
|
1537
|
+
}
|
|
1538
|
+
const args = await Promise.all(
|
|
1539
|
+
injections.map(async (injection) => {
|
|
1540
|
+
if (injection.type !== "unknown") {
|
|
1541
|
+
return getBuiltinInjectionValue(injection.type, ctx, body);
|
|
1542
|
+
}
|
|
1543
|
+
if (injectors && injection.name in injectors) {
|
|
1544
|
+
return await injectors[injection.name](ctx);
|
|
1545
|
+
}
|
|
1546
|
+
return void 0;
|
|
1547
|
+
})
|
|
1548
|
+
);
|
|
1549
|
+
return await handler(...args);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// src/runtime/invokeHandler.ts
|
|
1553
|
+
function mergeMeta(response, meta) {
|
|
1554
|
+
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
1555
|
+
if (!hasMeta) return response;
|
|
1556
|
+
const headers = new Headers(response.headers);
|
|
1557
|
+
for (const [key, value] of Object.entries(meta.headers)) {
|
|
1558
|
+
headers.set(key, value);
|
|
1559
|
+
}
|
|
1560
|
+
for (const cookie of meta.setCookies) {
|
|
1561
|
+
headers.append("set-cookie", cookie);
|
|
1562
|
+
}
|
|
1563
|
+
return new Response(response.body, {
|
|
1564
|
+
status: meta.status ?? response.status,
|
|
1565
|
+
headers
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
async function compose(middlewares, ctx, finalHandler) {
|
|
1569
|
+
const meta = ctx.meta;
|
|
1570
|
+
let index = -1;
|
|
1571
|
+
async function dispatch(i) {
|
|
1572
|
+
if (i <= index) {
|
|
1573
|
+
throw new Error("next() called multiple times");
|
|
1574
|
+
}
|
|
1575
|
+
index = i;
|
|
1576
|
+
if (i >= middlewares.length) {
|
|
1577
|
+
return await finalHandler();
|
|
1578
|
+
}
|
|
1579
|
+
const mw = middlewares[i];
|
|
1580
|
+
let innerResponse;
|
|
1581
|
+
const next = async () => {
|
|
1582
|
+
innerResponse = await dispatch(i + 1);
|
|
1583
|
+
return innerResponse;
|
|
1584
|
+
};
|
|
1585
|
+
const result = await mw(ctx, next);
|
|
1586
|
+
if (result instanceof Response) {
|
|
1587
|
+
return mergeMeta(result, meta);
|
|
1588
|
+
}
|
|
1589
|
+
if (innerResponse !== void 0) {
|
|
1590
|
+
return innerResponse;
|
|
1591
|
+
}
|
|
1592
|
+
throw new Error("\u4E2D\u95F4\u4EF6\u5FC5\u987B await next() \u6216\u8FD4\u56DE Response");
|
|
1593
|
+
}
|
|
1594
|
+
return await dispatch(0);
|
|
1595
|
+
}
|
|
1596
|
+
async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
1597
|
+
const meta = ctx.meta;
|
|
1598
|
+
const pickSseAndAutoClose = () => {
|
|
1599
|
+
const sseWriter = ctx.__sseWriter;
|
|
1600
|
+
if (!sseWriter) return null;
|
|
1601
|
+
if (!sseWriter.closed && !sseWriter.aborted) {
|
|
1602
|
+
sseWriter.close();
|
|
1603
|
+
}
|
|
1604
|
+
return mergeMeta(sseWriter.response, meta);
|
|
1605
|
+
};
|
|
1606
|
+
const autoCloseSseOnError = () => {
|
|
1607
|
+
const sseWriter = ctx.__sseWriter;
|
|
1608
|
+
if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {
|
|
1609
|
+
sseWriter.close();
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
if (!middlewares || middlewares.length === 0) {
|
|
1613
|
+
try {
|
|
1614
|
+
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
1615
|
+
const sseResponse = pickSseAndAutoClose();
|
|
1616
|
+
if (sseResponse) return sseResponse;
|
|
1617
|
+
return toResponse(result, meta);
|
|
1618
|
+
} catch (err) {
|
|
1619
|
+
autoCloseSseOnError();
|
|
1620
|
+
throw err;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
const finalHandler = async () => {
|
|
1624
|
+
try {
|
|
1625
|
+
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
1626
|
+
const sseResponse = pickSseAndAutoClose();
|
|
1627
|
+
if (sseResponse) return sseResponse;
|
|
1628
|
+
return toResponse(result, meta);
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
autoCloseSseOnError();
|
|
1631
|
+
throw err;
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
return await compose(middlewares, ctx, finalHandler);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/cli/createAppCore.ts
|
|
1638
|
+
import fs5 from "fs";
|
|
1639
|
+
import path8 from "path";
|
|
1640
|
+
import { PassThrough } from "stream";
|
|
1641
|
+
|
|
1642
|
+
// src/router/sortRoutes.ts
|
|
1643
|
+
function sortRoutes(routes) {
|
|
1644
|
+
return [...routes].sort((a, b) => {
|
|
1645
|
+
if (a.isDynamic !== b.isDynamic) {
|
|
1646
|
+
return a.isDynamic ? 1 : -1;
|
|
1647
|
+
}
|
|
1648
|
+
if (a.isCatchAll !== b.isCatchAll) {
|
|
1649
|
+
return a.isCatchAll ? 1 : -1;
|
|
1650
|
+
}
|
|
1651
|
+
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1652
|
+
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1653
|
+
if (aSegments !== bSegments) {
|
|
1654
|
+
return aSegments - bSegments;
|
|
1655
|
+
}
|
|
1656
|
+
return a.urlPath.localeCompare(b.urlPath);
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/router/detectRouteConflicts.ts
|
|
1661
|
+
function detectRouteConflicts(routes) {
|
|
1662
|
+
const map = /* @__PURE__ */ new Map();
|
|
1663
|
+
for (const route of routes) {
|
|
1664
|
+
const key = `${route.method} ${route.urlPath}`;
|
|
1665
|
+
const existing = map.get(key);
|
|
1666
|
+
if (existing) {
|
|
1667
|
+
existing.files.push(route.filePath);
|
|
1668
|
+
} else {
|
|
1669
|
+
map.set(key, {
|
|
1670
|
+
method: route.method,
|
|
1671
|
+
urlPath: route.urlPath,
|
|
1672
|
+
files: [route.filePath]
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
const conflicts = [];
|
|
1677
|
+
for (const conflict of map.values()) {
|
|
1678
|
+
if (conflict.files.length > 1) {
|
|
1679
|
+
conflicts.push(conflict);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return conflicts;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// src/server/createServer.ts
|
|
1686
|
+
import {
|
|
1687
|
+
createServer as createHttpServer
|
|
1688
|
+
} from "http";
|
|
1689
|
+
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
1690
|
+
import { readFileSync } from "fs";
|
|
1691
|
+
import { Readable as Readable2 } from "stream";
|
|
1692
|
+
import path6 from "path";
|
|
1693
|
+
|
|
1694
|
+
// src/router/matchRoute.ts
|
|
1695
|
+
function matchRoute(routes, method, path10) {
|
|
1696
|
+
for (const route of routes) {
|
|
1697
|
+
if (route.method !== method) {
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
if (!route.isDynamic) {
|
|
1701
|
+
if (route.urlPath === path10) {
|
|
1702
|
+
return { route, params: {} };
|
|
1703
|
+
}
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const params = matchDynamicPath(route.urlPath, path10, route.paramNames, route.isCatchAll);
|
|
1707
|
+
if (params !== null) {
|
|
1708
|
+
return { route, params };
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return null;
|
|
1712
|
+
}
|
|
1713
|
+
function matchWsRoute(wsRoutes, path10) {
|
|
1714
|
+
for (const route of wsRoutes) {
|
|
1715
|
+
if (!route.isDynamic) {
|
|
1716
|
+
if (route.urlPath === path10) {
|
|
1717
|
+
return { route, params: {} };
|
|
1718
|
+
}
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
const params = matchDynamicPath(route.urlPath, path10, route.paramNames, route.isCatchAll);
|
|
1722
|
+
if (params !== null) {
|
|
1723
|
+
return { route, params };
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
function matchDynamicPath(pattern, path10, paramNames, isCatchAll) {
|
|
1729
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
1730
|
+
const pathSegments = path10.split("/").filter(Boolean);
|
|
1731
|
+
if (isCatchAll) {
|
|
1732
|
+
const nonCatchAllCount = patternSegments.length - 1;
|
|
1733
|
+
if (pathSegments.length <= nonCatchAllCount) {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
const params2 = {};
|
|
1737
|
+
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
1738
|
+
const patternSeg = patternSegments[i];
|
|
1739
|
+
const pathSeg = pathSegments[i];
|
|
1740
|
+
if (patternSeg.startsWith(":")) {
|
|
1741
|
+
const paramName = patternSeg.slice(1);
|
|
1742
|
+
params2[paramName] = pathSeg;
|
|
1743
|
+
} else if (patternSeg !== pathSeg) {
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
1748
|
+
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
1749
|
+
params2[catchAllParamName] = catchAllValue;
|
|
1750
|
+
if (Object.keys(params2).length !== paramNames.length) {
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
return params2;
|
|
1754
|
+
}
|
|
1755
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
const params = {};
|
|
1759
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
1760
|
+
const patternSeg = patternSegments[i];
|
|
1761
|
+
const pathSeg = pathSegments[i];
|
|
1762
|
+
if (patternSeg.startsWith(":")) {
|
|
1763
|
+
const paramName = patternSeg.slice(1);
|
|
1764
|
+
params[paramName] = pathSeg;
|
|
1765
|
+
} else if (patternSeg !== pathSeg) {
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
if (Object.keys(params).length !== paramNames.length) {
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
return params;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/loader/resolveExports.ts
|
|
1776
|
+
function resolveExport(module, exportName) {
|
|
1777
|
+
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
1778
|
+
return module[exportName];
|
|
1779
|
+
}
|
|
1780
|
+
const defaultExport = module.default;
|
|
1781
|
+
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
1782
|
+
const value = defaultExport[exportName];
|
|
1783
|
+
if (value !== void 0) {
|
|
1784
|
+
return value;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return void 0;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// src/loader/validateRouteModule.ts
|
|
1791
|
+
function validateRouteModule(value, method, filePath) {
|
|
1792
|
+
if (typeof value !== "function") {
|
|
1793
|
+
throw new Error(
|
|
1794
|
+
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// src/loader/loadRouteModule.ts
|
|
1800
|
+
async function loadRouteModule(filePath, method) {
|
|
1801
|
+
let module;
|
|
1802
|
+
try {
|
|
1803
|
+
module = await importWithCacheBust(filePath);
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1806
|
+
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
1483
1807
|
}
|
|
1484
|
-
|
|
1808
|
+
const handler = resolveExport(module, method);
|
|
1809
|
+
validateRouteModule(handler, method, filePath);
|
|
1810
|
+
return { handler, method };
|
|
1485
1811
|
}
|
|
1486
1812
|
|
|
1487
1813
|
// src/utils/parseJsonBody.ts
|
|
@@ -1564,246 +1890,6 @@ async function resolveInput(method, request) {
|
|
|
1564
1890
|
return queryToObject(url.searchParams);
|
|
1565
1891
|
}
|
|
1566
1892
|
|
|
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);
|
|
1591
|
-
}
|
|
1592
|
-
};
|
|
1593
|
-
if (value instanceof Response) {
|
|
1594
|
-
if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
|
|
1595
|
-
const headers2 = new Headers(value.headers);
|
|
1596
|
-
applyMeta(headers2);
|
|
1597
|
-
return new Response(value.body, {
|
|
1598
|
-
status: meta.status ?? value.status,
|
|
1599
|
-
headers: headers2
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
return value;
|
|
1603
|
-
}
|
|
1604
|
-
if (value === null || value === void 0) {
|
|
1605
|
-
const status = meta?.status ?? 204;
|
|
1606
|
-
const headers2 = new Headers();
|
|
1607
|
-
applyMeta(headers2);
|
|
1608
|
-
return new Response(null, { status, headers: headers2 });
|
|
1609
|
-
}
|
|
1610
|
-
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
1611
|
-
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1612
|
-
applyMeta(headers2);
|
|
1613
|
-
return new Response(value, {
|
|
1614
|
-
status: meta?.status ?? 200,
|
|
1615
|
-
headers: headers2
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1619
|
-
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1620
|
-
applyMeta(headers2);
|
|
1621
|
-
return new Response(value, {
|
|
1622
|
-
status: meta?.status ?? 200,
|
|
1623
|
-
headers: headers2
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
if (value instanceof Uint8Array) {
|
|
1627
|
-
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
1628
|
-
applyMeta(headers2);
|
|
1629
|
-
return new Response(value, {
|
|
1630
|
-
status: meta?.status ?? 200,
|
|
1631
|
-
headers: headers2
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
if (isPlainObject(value) || Array.isArray(value)) {
|
|
1635
|
-
const body2 = JSON.stringify(value);
|
|
1636
|
-
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
1637
|
-
applyMeta(headers2);
|
|
1638
|
-
return new Response(body2, {
|
|
1639
|
-
status: meta?.status ?? 200,
|
|
1640
|
-
headers: headers2
|
|
1641
|
-
});
|
|
1642
|
-
}
|
|
1643
|
-
if (typeof value === "string") {
|
|
1644
|
-
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1645
|
-
applyMeta(headers2);
|
|
1646
|
-
return new Response(value, {
|
|
1647
|
-
status: meta?.status ?? 200,
|
|
1648
|
-
headers: headers2
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
1652
|
-
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1653
|
-
applyMeta(headers2);
|
|
1654
|
-
return new Response(String(value), {
|
|
1655
|
-
status: meta?.status ?? 200,
|
|
1656
|
-
headers: headers2
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
const body = JSON.stringify(value);
|
|
1660
|
-
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1661
|
-
applyMeta(headers);
|
|
1662
|
-
return new Response(body, {
|
|
1663
|
-
status: meta?.status ?? 200,
|
|
1664
|
-
headers
|
|
1665
|
-
});
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// src/injection/injectParams.ts
|
|
1669
|
-
function getBuiltinInjectionValue(type, ctx, body) {
|
|
1670
|
-
switch (type) {
|
|
1671
|
-
case "query":
|
|
1672
|
-
return queryToObject(ctx.query);
|
|
1673
|
-
case "params":
|
|
1674
|
-
return ctx.params;
|
|
1675
|
-
case "headers":
|
|
1676
|
-
return ctx.headers;
|
|
1677
|
-
case "context":
|
|
1678
|
-
return ctx;
|
|
1679
|
-
case "cookies":
|
|
1680
|
-
return ctx.cookies;
|
|
1681
|
-
case "ip":
|
|
1682
|
-
return ctx.ip;
|
|
1683
|
-
case "body":
|
|
1684
|
-
return body;
|
|
1685
|
-
// form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
|
|
1686
|
-
// 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
|
|
1687
|
-
case "form":
|
|
1688
|
-
return body;
|
|
1689
|
-
case "files":
|
|
1690
|
-
if (body && typeof body === "object" && "files" in body) {
|
|
1691
|
-
return body.files;
|
|
1692
|
-
}
|
|
1693
|
-
return [];
|
|
1694
|
-
case "fields":
|
|
1695
|
-
if (body && typeof body === "object" && "fields" in body) {
|
|
1696
|
-
return body.fields;
|
|
1697
|
-
}
|
|
1698
|
-
return {};
|
|
1699
|
-
default:
|
|
1700
|
-
return void 0;
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
1704
|
-
const injections = resolveInjection(handler);
|
|
1705
|
-
if (injections.length === 0) {
|
|
1706
|
-
return await handler();
|
|
1707
|
-
}
|
|
1708
|
-
const args = await Promise.all(
|
|
1709
|
-
injections.map(async (injection) => {
|
|
1710
|
-
if (injection.type !== "unknown") {
|
|
1711
|
-
return getBuiltinInjectionValue(injection.type, ctx, body);
|
|
1712
|
-
}
|
|
1713
|
-
if (injectors && injection.name in injectors) {
|
|
1714
|
-
return await injectors[injection.name](ctx);
|
|
1715
|
-
}
|
|
1716
|
-
return void 0;
|
|
1717
|
-
})
|
|
1718
|
-
);
|
|
1719
|
-
return await handler(...args);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// src/runtime/invokeHandler.ts
|
|
1723
|
-
function mergeMeta(response, meta) {
|
|
1724
|
-
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
1725
|
-
if (!hasMeta) return response;
|
|
1726
|
-
const headers = new Headers(response.headers);
|
|
1727
|
-
for (const [key, value] of Object.entries(meta.headers)) {
|
|
1728
|
-
headers.set(key, value);
|
|
1729
|
-
}
|
|
1730
|
-
for (const cookie of meta.setCookies) {
|
|
1731
|
-
headers.append("set-cookie", cookie);
|
|
1732
|
-
}
|
|
1733
|
-
return new Response(response.body, {
|
|
1734
|
-
status: meta.status ?? response.status,
|
|
1735
|
-
headers
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
async function compose(middlewares, ctx, finalHandler) {
|
|
1739
|
-
const meta = ctx.meta;
|
|
1740
|
-
let index = -1;
|
|
1741
|
-
async function dispatch(i) {
|
|
1742
|
-
if (i <= index) {
|
|
1743
|
-
throw new Error("next() called multiple times");
|
|
1744
|
-
}
|
|
1745
|
-
index = i;
|
|
1746
|
-
if (i >= middlewares.length) {
|
|
1747
|
-
return await finalHandler();
|
|
1748
|
-
}
|
|
1749
|
-
const mw = middlewares[i];
|
|
1750
|
-
let innerResponse;
|
|
1751
|
-
const next = async () => {
|
|
1752
|
-
innerResponse = await dispatch(i + 1);
|
|
1753
|
-
return innerResponse;
|
|
1754
|
-
};
|
|
1755
|
-
const result = await mw(ctx, next);
|
|
1756
|
-
if (result instanceof Response) {
|
|
1757
|
-
return mergeMeta(result, meta);
|
|
1758
|
-
}
|
|
1759
|
-
if (innerResponse !== void 0) {
|
|
1760
|
-
return innerResponse;
|
|
1761
|
-
}
|
|
1762
|
-
throw new Error("\u4E2D\u95F4\u4EF6\u5FC5\u987B await next() \u6216\u8FD4\u56DE Response");
|
|
1763
|
-
}
|
|
1764
|
-
return await dispatch(0);
|
|
1765
|
-
}
|
|
1766
|
-
async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
1767
|
-
const meta = ctx.meta;
|
|
1768
|
-
const pickSseAndAutoClose = () => {
|
|
1769
|
-
const sseWriter = ctx.__sseWriter;
|
|
1770
|
-
if (!sseWriter) return null;
|
|
1771
|
-
if (!sseWriter.closed && !sseWriter.aborted) {
|
|
1772
|
-
sseWriter.close();
|
|
1773
|
-
}
|
|
1774
|
-
return mergeMeta(sseWriter.response, meta);
|
|
1775
|
-
};
|
|
1776
|
-
const autoCloseSseOnError = () => {
|
|
1777
|
-
const sseWriter = ctx.__sseWriter;
|
|
1778
|
-
if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {
|
|
1779
|
-
sseWriter.close();
|
|
1780
|
-
}
|
|
1781
|
-
};
|
|
1782
|
-
if (!middlewares || middlewares.length === 0) {
|
|
1783
|
-
try {
|
|
1784
|
-
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
1785
|
-
const sseResponse = pickSseAndAutoClose();
|
|
1786
|
-
if (sseResponse) return sseResponse;
|
|
1787
|
-
return toResponse(result, meta);
|
|
1788
|
-
} catch (err) {
|
|
1789
|
-
autoCloseSseOnError();
|
|
1790
|
-
throw err;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
const finalHandler = async () => {
|
|
1794
|
-
try {
|
|
1795
|
-
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
1796
|
-
const sseResponse = pickSseAndAutoClose();
|
|
1797
|
-
if (sseResponse) return sseResponse;
|
|
1798
|
-
return toResponse(result, meta);
|
|
1799
|
-
} catch (err) {
|
|
1800
|
-
autoCloseSseOnError();
|
|
1801
|
-
throw err;
|
|
1802
|
-
}
|
|
1803
|
-
};
|
|
1804
|
-
return await compose(middlewares, ctx, finalHandler);
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
1893
|
// src/response/sendNodeResponse.ts
|
|
1808
1894
|
import { Readable } from "stream";
|
|
1809
1895
|
async function sendNodeResponse(response, res) {
|
|
@@ -1872,9 +1958,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
1872
1958
|
function mapZodIssues(error) {
|
|
1873
1959
|
return error.issues.map((issue) => {
|
|
1874
1960
|
const code = mapZodCode(issue.code, issue.message);
|
|
1875
|
-
const
|
|
1961
|
+
const path10 = issue.path.map(String).join(".") || "";
|
|
1876
1962
|
return {
|
|
1877
|
-
path:
|
|
1963
|
+
path: path10,
|
|
1878
1964
|
code,
|
|
1879
1965
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
1880
1966
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -1934,7 +2020,7 @@ function getClientIp(req) {
|
|
|
1934
2020
|
|
|
1935
2021
|
// src/server/handleWsUpgrade.ts
|
|
1936
2022
|
import { WebSocketServer, WebSocket } from "ws";
|
|
1937
|
-
import
|
|
2023
|
+
import path4 from "path";
|
|
1938
2024
|
|
|
1939
2025
|
// src/errors/formatErrorResponse.ts
|
|
1940
2026
|
function formatErrorResponse(error) {
|
|
@@ -2107,7 +2193,7 @@ function attachWebSocket(options) {
|
|
|
2107
2193
|
const finalHandler = async () => {
|
|
2108
2194
|
let handlers;
|
|
2109
2195
|
try {
|
|
2110
|
-
const absoluteFilePath =
|
|
2196
|
+
const absoluteFilePath = path4.resolve(rootDir, route.filePath);
|
|
2111
2197
|
handlers = await loadWsHandler(absoluteFilePath, ctx);
|
|
2112
2198
|
} catch (err) {
|
|
2113
2199
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -2153,8 +2239,8 @@ function attachWebSocket(options) {
|
|
|
2153
2239
|
}
|
|
2154
2240
|
|
|
2155
2241
|
// src/cli/generateSchemaFiles.ts
|
|
2156
|
-
import
|
|
2157
|
-
import
|
|
2242
|
+
import path5 from "path";
|
|
2243
|
+
import fs3 from "fs/promises";
|
|
2158
2244
|
|
|
2159
2245
|
// src/ast/generateZodSchema.ts
|
|
2160
2246
|
var CodeGenContext = class {
|
|
@@ -2463,7 +2549,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
2463
2549
|
}
|
|
2464
2550
|
const idx = rel.lastIndexOf("/");
|
|
2465
2551
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2466
|
-
return
|
|
2552
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
2467
2553
|
}
|
|
2468
2554
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
2469
2555
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -2474,7 +2560,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
2474
2560
|
}
|
|
2475
2561
|
const idx = rel.lastIndexOf("/");
|
|
2476
2562
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2477
|
-
return
|
|
2563
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
2478
2564
|
}
|
|
2479
2565
|
function getHelpersImportPath(relDir) {
|
|
2480
2566
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -2524,7 +2610,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
2524
2610
|
}
|
|
2525
2611
|
const fileEntries = [];
|
|
2526
2612
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2527
|
-
const relFile =
|
|
2613
|
+
const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2528
2614
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
2529
2615
|
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
2530
2616
|
let relForDir = relFile;
|
|
@@ -2539,7 +2625,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
2539
2625
|
}
|
|
2540
2626
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2541
2627
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
2542
|
-
const helpersPath =
|
|
2628
|
+
const helpersPath = path5.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
2543
2629
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
2544
2630
|
}
|
|
2545
2631
|
await Promise.all(
|
|
@@ -2547,8 +2633,8 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
2547
2633
|
);
|
|
2548
2634
|
}
|
|
2549
2635
|
async function writeSchemaFile(outputPath, source) {
|
|
2550
|
-
await
|
|
2551
|
-
await
|
|
2636
|
+
await fs3.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
2637
|
+
await fs3.writeFile(outputPath, source, "utf-8");
|
|
2552
2638
|
}
|
|
2553
2639
|
|
|
2554
2640
|
// src/server/createServer.ts
|
|
@@ -2596,15 +2682,15 @@ function limitStreamSize(stream, maxSize) {
|
|
|
2596
2682
|
}
|
|
2597
2683
|
});
|
|
2598
2684
|
}
|
|
2599
|
-
function findAllowedMethods(routes,
|
|
2685
|
+
function findAllowedMethods(routes, path10) {
|
|
2600
2686
|
const methods = /* @__PURE__ */ new Set();
|
|
2601
2687
|
for (const route of routes) {
|
|
2602
|
-
if (route.urlPath ===
|
|
2688
|
+
if (route.urlPath === path10) {
|
|
2603
2689
|
methods.add(route.method);
|
|
2604
2690
|
continue;
|
|
2605
2691
|
}
|
|
2606
2692
|
if (route.isDynamic) {
|
|
2607
|
-
const params = matchDynamicPath(route.urlPath,
|
|
2693
|
+
const params = matchDynamicPath(route.urlPath, path10, route.paramNames, route.isCatchAll);
|
|
2608
2694
|
if (params !== null) {
|
|
2609
2695
|
methods.add(route.method);
|
|
2610
2696
|
}
|
|
@@ -2690,7 +2776,7 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
2690
2776
|
}
|
|
2691
2777
|
ctx.params = match.params;
|
|
2692
2778
|
const { route } = match;
|
|
2693
|
-
const absoluteFilePath =
|
|
2779
|
+
const absoluteFilePath = path6.resolve(rootDir, route.filePath);
|
|
2694
2780
|
const routeModule = await loadRouteModule(absoluteFilePath, route.method);
|
|
2695
2781
|
const input = await resolveInput(route.method, request);
|
|
2696
2782
|
const inputType = getInputTypeForMethod(route.method);
|
|
@@ -2764,8 +2850,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
2764
2850
|
}
|
|
2765
2851
|
|
|
2766
2852
|
// src/cli/generateRoutes.ts
|
|
2767
|
-
import
|
|
2768
|
-
import
|
|
2853
|
+
import fs4 from "fs";
|
|
2854
|
+
import path7 from "path";
|
|
2769
2855
|
|
|
2770
2856
|
// src/middleware/loadMiddlewares.ts
|
|
2771
2857
|
var middlewareCache = /* @__PURE__ */ new Map();
|
|
@@ -2921,7 +3007,7 @@ function resolveDeclaration(decl) {
|
|
|
2921
3007
|
}
|
|
2922
3008
|
|
|
2923
3009
|
// src/cli/createAppCore.ts
|
|
2924
|
-
var DEFAULT_DIST = "
|
|
3010
|
+
var DEFAULT_DIST = "dist";
|
|
2925
3011
|
var DEFAULT_PORT = 3e3;
|
|
2926
3012
|
var ROUTES_FILE = "faapi-routes.js";
|
|
2927
3013
|
var PATTERNS = ["src/api/**/*.ts"];
|
|
@@ -2943,8 +3029,8 @@ function isFaapiConfigKey(key) {
|
|
|
2943
3029
|
async function createAppBase(options) {
|
|
2944
3030
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
2945
3031
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
2946
|
-
const routesPath =
|
|
2947
|
-
if (!
|
|
3032
|
+
const routesPath = path8.resolve(rootDir, dist, ROUTES_FILE);
|
|
3033
|
+
if (!fs5.existsSync(routesPath)) {
|
|
2948
3034
|
throw new Error(
|
|
2949
3035
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
2950
3036
|
);
|
|
@@ -3147,8 +3233,8 @@ async function createAppBase(options) {
|
|
|
3147
3233
|
|
|
3148
3234
|
// src/router/scanRoutes.ts
|
|
3149
3235
|
import fg from "fast-glob";
|
|
3150
|
-
import
|
|
3151
|
-
import
|
|
3236
|
+
import path9 from "path";
|
|
3237
|
+
import fs6 from "fs";
|
|
3152
3238
|
|
|
3153
3239
|
// src/router/constants.ts
|
|
3154
3240
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
@@ -3158,9 +3244,9 @@ function isHttpMethod(value) {
|
|
|
3158
3244
|
}
|
|
3159
3245
|
|
|
3160
3246
|
// src/utils/normalizePath.ts
|
|
3161
|
-
function normalizePath(
|
|
3162
|
-
if (!
|
|
3163
|
-
let result =
|
|
3247
|
+
function normalizePath(path10) {
|
|
3248
|
+
if (!path10) return "";
|
|
3249
|
+
let result = path10.replace(/\\/g, "/");
|
|
3164
3250
|
result = result.replace(/\/+/g, "/");
|
|
3165
3251
|
result = result.replace(/\/+$/, "");
|
|
3166
3252
|
if (result && !result.startsWith("/")) {
|
|
@@ -3209,38 +3295,38 @@ function filePathToUrlPath(filePath) {
|
|
|
3209
3295
|
// src/router/scanRoutes.ts
|
|
3210
3296
|
var APP_DIR = "src";
|
|
3211
3297
|
function toProdAbsPath(sourceAbsPath, rootDir, dist) {
|
|
3212
|
-
let rel =
|
|
3298
|
+
let rel = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3213
3299
|
if (rel.startsWith(`${APP_DIR}/`)) {
|
|
3214
3300
|
rel = rel.slice(APP_DIR.length + 1);
|
|
3215
3301
|
}
|
|
3216
3302
|
const prodRel = `${dist}/${rel.replace(/\.ts$/, ".js")}`;
|
|
3217
|
-
return
|
|
3303
|
+
return path9.resolve(rootDir, prodRel);
|
|
3218
3304
|
}
|
|
3219
3305
|
async function findMergedMiddlewares(routeFilePath, rootDir, dist) {
|
|
3220
|
-
const routeDir =
|
|
3221
|
-
const resolvedRoot =
|
|
3306
|
+
const routeDir = path9.dirname(routeFilePath);
|
|
3307
|
+
const resolvedRoot = path9.resolve(rootDir);
|
|
3222
3308
|
const mwPaths = [];
|
|
3223
|
-
let currentDir =
|
|
3309
|
+
let currentDir = path9.resolve(rootDir, routeDir);
|
|
3224
3310
|
while (true) {
|
|
3225
3311
|
if (dist) {
|
|
3226
|
-
const mwPath =
|
|
3227
|
-
const absMwPath =
|
|
3312
|
+
const mwPath = path9.join(currentDir, "middlewares.js");
|
|
3313
|
+
const absMwPath = path9.resolve(rootDir, mwPath);
|
|
3228
3314
|
const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, dist);
|
|
3229
|
-
if (
|
|
3315
|
+
if (fs6.existsSync(prodAbsMwPath)) {
|
|
3230
3316
|
mwPaths.push(prodAbsMwPath);
|
|
3231
3317
|
}
|
|
3232
3318
|
} else {
|
|
3233
3319
|
for (const ext of [".ts", ".js"]) {
|
|
3234
|
-
const mwPath =
|
|
3235
|
-
const absMwPath =
|
|
3236
|
-
if (
|
|
3320
|
+
const mwPath = path9.join(currentDir, `middlewares${ext}`);
|
|
3321
|
+
const absMwPath = path9.resolve(rootDir, mwPath);
|
|
3322
|
+
if (fs6.existsSync(absMwPath)) {
|
|
3237
3323
|
mwPaths.push(absMwPath);
|
|
3238
3324
|
break;
|
|
3239
3325
|
}
|
|
3240
3326
|
}
|
|
3241
3327
|
}
|
|
3242
3328
|
if (currentDir === resolvedRoot) break;
|
|
3243
|
-
const parentDir =
|
|
3329
|
+
const parentDir = path9.dirname(currentDir);
|
|
3244
3330
|
if (parentDir === currentDir) break;
|
|
3245
3331
|
currentDir = parentDir;
|
|
3246
3332
|
}
|
|
@@ -3302,7 +3388,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3302
3388
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
3303
3389
|
const fileName = normalizedFile.split("/").pop();
|
|
3304
3390
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
3305
|
-
const absPath =
|
|
3391
|
+
const absPath = path9.resolve(rootDir, normalizedFile);
|
|
3306
3392
|
const importPath = dist ? toProdAbsPath(absPath, rootDir, dist) : absPath;
|
|
3307
3393
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
3308
3394
|
const paramNames = extractParamNames(urlPath);
|
|
@@ -3373,6 +3459,7 @@ export {
|
|
|
3373
3459
|
collectRouteSchemaSources,
|
|
3374
3460
|
cors,
|
|
3375
3461
|
createProdApp as createApp,
|
|
3462
|
+
createContext,
|
|
3376
3463
|
createDevApp,
|
|
3377
3464
|
createProdApp,
|
|
3378
3465
|
createProgram,
|
|
@@ -3380,7 +3467,9 @@ export {
|
|
|
3380
3467
|
getInputTypeForMethod,
|
|
3381
3468
|
helmet,
|
|
3382
3469
|
invalidateProgramCache,
|
|
3470
|
+
invokeHandler,
|
|
3383
3471
|
loadConfig,
|
|
3472
|
+
loadEnv,
|
|
3384
3473
|
logger,
|
|
3385
3474
|
resolveTypeNode
|
|
3386
3475
|
};
|