@faapi/faapi 0.0.0-canary.2a7b6a3 → 0.0.0-canary.4e89b9b

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/index.js CHANGED
@@ -290,10 +290,35 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
290
290
  const properties = typeName === "Pick" ? innerType.properties.filter((p) => keySet.has(p.name)) : innerType.properties.filter((p) => !keySet.has(p.name));
291
291
  return { kind: "object", properties };
292
292
  }
293
- if (typeName === "Map" || typeName === "Set" || typeName === "WeakMap" || typeName === "WeakSet") {
293
+ if (typeName === "Map") {
294
+ if (!typeNode.typeArguments || typeNode.typeArguments.length !== 2) {
295
+ throw new SchemaExtractionError(
296
+ typeNode.getText(),
297
+ "Map \u5FC5\u987B\u5E26 2 \u4E2A\u7C7B\u578B\u53C2\u6570\uFF0C\u5982 Map<K, V>\uFF0C\u88F8 Map \u4E0D\u652F\u6301"
298
+ );
299
+ }
300
+ return {
301
+ kind: "map",
302
+ key: resolveTypeNode(typeNode.typeArguments[0], checker, visited),
303
+ value: resolveTypeNode(typeNode.typeArguments[1], checker, visited)
304
+ };
305
+ }
306
+ if (typeName === "Set") {
307
+ if (!typeNode.typeArguments || typeNode.typeArguments.length !== 1) {
308
+ throw new SchemaExtractionError(
309
+ typeNode.getText(),
310
+ "Set \u5FC5\u987B\u5E26 1 \u4E2A\u7C7B\u578B\u53C2\u6570\uFF0C\u5982 Set<T>\uFF0C\u88F8 Set \u4E0D\u652F\u6301"
311
+ );
312
+ }
313
+ return {
314
+ kind: "set",
315
+ element: resolveTypeNode(typeNode.typeArguments[0], checker, visited)
316
+ };
317
+ }
318
+ if (typeName === "WeakMap" || typeName === "WeakSet") {
294
319
  throw new SchemaExtractionError(
295
320
  typeNode.getText(),
296
- `${typeName} \u8FD0\u884C\u65F6\u65E0\u6CD5\u6821\u9A8C\uFF0C\u8BF7\u6539\u7528\u5BF9\u8C61\u6216\u6570\u7EC4`
321
+ `${typeName} \u8FD0\u884C\u65F6\u65E0\u6CD5\u679A\u4E3E\u6821\u9A8C\uFF0C\u8BF7\u6539\u7528 Map / Set \u6216\u5BF9\u8C61`
297
322
  );
298
323
  }
299
324
  if (typeName === "Promise") {
@@ -650,6 +675,7 @@ import ts4 from "typescript";
650
675
  var PARAM_TYPE_MAP = {
651
676
  query: "query",
652
677
  body: "body",
678
+ form: "form",
653
679
  headers: "headers",
654
680
  params: "params",
655
681
  context: "context",
@@ -807,9 +833,16 @@ function collectRouteSchemaSources(routes, rootDir) {
807
833
  const inputType = getInputTypeForMethod(method);
808
834
  const schemaName = getSchemaName(method, inputType);
809
835
  const meta = analyzeInjection(code, method);
810
- const param = meta.params.find((p) => p.type === inputType);
836
+ const param = meta.params.find((p) => p.type === inputType) ?? (inputType === "body" ? meta.params.find((p) => p.type === "form") : void 0);
837
+ const isForm = param?.type === "form";
811
838
  const typeInfo = param?.typeName ? extractTypeInfo(program, filePath, param.typeName) : null;
812
- sources.push({ urlPath: entry.urlPath, filePath, schemaName, typeInfo });
839
+ sources.push({
840
+ urlPath: entry.urlPath,
841
+ filePath,
842
+ schemaName,
843
+ typeInfo,
844
+ coerce: isForm || void 0
845
+ });
813
846
  }
814
847
  }
815
848
  return { sources, allTypesByFile, mergedAllTypes };
@@ -882,8 +915,8 @@ function cors(options = {}) {
882
915
 
883
916
  // src/middleware/logger.ts
884
917
  function logger(options = {}) {
885
- const { log = console.log } = options;
886
918
  return async (ctx, next) => {
919
+ const log = options.log ?? console.log;
887
920
  const start = Date.now();
888
921
  try {
889
922
  const response = await next();
@@ -995,8 +1028,8 @@ async function importWithCacheBust(filePath) {
995
1028
 
996
1029
  // src/config/loadConfig.ts
997
1030
  var CONFIG_PRODUCT_FILE = "faapi-config.js";
998
- async function loadConfig(rootDir, outDir) {
999
- const configProductPath = path2.resolve(rootDir, outDir, CONFIG_PRODUCT_FILE);
1031
+ async function loadConfig(rootDir, dist) {
1032
+ const configProductPath = path2.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
1000
1033
  if (fs.existsSync(configProductPath)) {
1001
1034
  const module = await importWithCacheBust(configProductPath);
1002
1035
  return module.default ?? {};
@@ -1004,7 +1037,7 @@ async function loadConfig(rootDir, outDir) {
1004
1037
  const hasSourceConfig = fs.existsSync(path2.join(rootDir, "faapi.config.ts")) || fs.existsSync(path2.join(rootDir, "faapi.config.js"));
1005
1038
  if (hasSourceConfig) {
1006
1039
  throw new Error(
1007
- `[faapi] ${outDir}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
1040
+ `[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
1008
1041
  );
1009
1042
  }
1010
1043
  return null;
@@ -1062,182 +1095,6 @@ var ModuleLoadError = class extends FaapiError {
1062
1095
  }
1063
1096
  };
1064
1097
 
1065
- // src/cli/createAppCore.ts
1066
- import fs4 from "fs";
1067
- import path7 from "path";
1068
- import { PassThrough } from "stream";
1069
-
1070
- // src/router/sortRoutes.ts
1071
- function sortRoutes(routes) {
1072
- return [...routes].sort((a, b) => {
1073
- if (a.isDynamic !== b.isDynamic) {
1074
- return a.isDynamic ? 1 : -1;
1075
- }
1076
- if (a.isCatchAll !== b.isCatchAll) {
1077
- return a.isCatchAll ? 1 : -1;
1078
- }
1079
- const aSegments = a.urlPath.split("/").filter(Boolean).length;
1080
- const bSegments = b.urlPath.split("/").filter(Boolean).length;
1081
- if (aSegments !== bSegments) {
1082
- return aSegments - bSegments;
1083
- }
1084
- return a.urlPath.localeCompare(b.urlPath);
1085
- });
1086
- }
1087
-
1088
- // src/router/detectRouteConflicts.ts
1089
- function detectRouteConflicts(routes) {
1090
- const map = /* @__PURE__ */ new Map();
1091
- for (const route of routes) {
1092
- const key = `${route.method} ${route.urlPath}`;
1093
- const existing = map.get(key);
1094
- if (existing) {
1095
- existing.files.push(route.filePath);
1096
- } else {
1097
- map.set(key, {
1098
- method: route.method,
1099
- urlPath: route.urlPath,
1100
- files: [route.filePath]
1101
- });
1102
- }
1103
- }
1104
- const conflicts = [];
1105
- for (const conflict of map.values()) {
1106
- if (conflict.files.length > 1) {
1107
- conflicts.push(conflict);
1108
- }
1109
- }
1110
- return conflicts;
1111
- }
1112
-
1113
- // src/server/createServer.ts
1114
- import {
1115
- createServer as createHttpServer
1116
- } from "http";
1117
- import { createSecureServer as createHttp2SecureServer } from "http2";
1118
- import { readFileSync } from "fs";
1119
- import { Readable as Readable2 } from "stream";
1120
- import path5 from "path";
1121
-
1122
- // src/router/matchRoute.ts
1123
- function matchRoute(routes, method, path9) {
1124
- for (const route of routes) {
1125
- if (route.method !== method) {
1126
- continue;
1127
- }
1128
- if (!route.isDynamic) {
1129
- if (route.urlPath === path9) {
1130
- return { route, params: {} };
1131
- }
1132
- continue;
1133
- }
1134
- const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1135
- if (params !== null) {
1136
- return { route, params };
1137
- }
1138
- }
1139
- return null;
1140
- }
1141
- function matchWsRoute(wsRoutes, path9) {
1142
- for (const route of wsRoutes) {
1143
- if (!route.isDynamic) {
1144
- if (route.urlPath === path9) {
1145
- return { route, params: {} };
1146
- }
1147
- continue;
1148
- }
1149
- const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1150
- if (params !== null) {
1151
- return { route, params };
1152
- }
1153
- }
1154
- return null;
1155
- }
1156
- function matchDynamicPath(pattern, path9, paramNames, isCatchAll) {
1157
- const patternSegments = pattern.split("/").filter(Boolean);
1158
- const pathSegments = path9.split("/").filter(Boolean);
1159
- if (isCatchAll) {
1160
- const nonCatchAllCount = patternSegments.length - 1;
1161
- if (pathSegments.length <= nonCatchAllCount) {
1162
- return null;
1163
- }
1164
- const params2 = {};
1165
- for (let i = 0; i < nonCatchAllCount; i++) {
1166
- const patternSeg = patternSegments[i];
1167
- const pathSeg = pathSegments[i];
1168
- if (patternSeg.startsWith(":")) {
1169
- const paramName = patternSeg.slice(1);
1170
- params2[paramName] = pathSeg;
1171
- } else if (patternSeg !== pathSeg) {
1172
- return null;
1173
- }
1174
- }
1175
- const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
1176
- const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
1177
- params2[catchAllParamName] = catchAllValue;
1178
- if (Object.keys(params2).length !== paramNames.length) {
1179
- return null;
1180
- }
1181
- return params2;
1182
- }
1183
- if (patternSegments.length !== pathSegments.length) {
1184
- return null;
1185
- }
1186
- const params = {};
1187
- for (let i = 0; i < patternSegments.length; i++) {
1188
- const patternSeg = patternSegments[i];
1189
- const pathSeg = pathSegments[i];
1190
- if (patternSeg.startsWith(":")) {
1191
- const paramName = patternSeg.slice(1);
1192
- params[paramName] = pathSeg;
1193
- } else if (patternSeg !== pathSeg) {
1194
- return null;
1195
- }
1196
- }
1197
- if (Object.keys(params).length !== paramNames.length) {
1198
- return null;
1199
- }
1200
- return params;
1201
- }
1202
-
1203
- // src/loader/resolveExports.ts
1204
- function resolveExport(module, exportName) {
1205
- if (exportName in module && typeof module[exportName] !== "undefined") {
1206
- return module[exportName];
1207
- }
1208
- const defaultExport = module.default;
1209
- if (defaultExport !== null && typeof defaultExport === "object") {
1210
- const value = defaultExport[exportName];
1211
- if (value !== void 0) {
1212
- return value;
1213
- }
1214
- }
1215
- return void 0;
1216
- }
1217
-
1218
- // src/loader/validateRouteModule.ts
1219
- function validateRouteModule(value, method, filePath) {
1220
- if (typeof value !== "function") {
1221
- throw new Error(
1222
- `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
1223
- );
1224
- }
1225
- }
1226
-
1227
- // src/loader/loadRouteModule.ts
1228
- async function loadRouteModule(filePath, method) {
1229
- let module;
1230
- try {
1231
- module = await importWithCacheBust(filePath);
1232
- } catch (err) {
1233
- const reason = err instanceof Error ? err.message : String(err);
1234
- throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
1235
- }
1236
- const handler = resolveExport(module, method);
1237
- validateRouteModule(handler, method, filePath);
1238
- return { handler, method };
1239
- }
1240
-
1241
1098
  // src/runtime/sse.ts
1242
1099
  function encodeSseEvent(event) {
1243
1100
  let out = "";
@@ -1442,111 +1299,22 @@ function createContext(request, params, config = {}, ip = "") {
1442
1299
  return ctx;
1443
1300
  }
1444
1301
 
1445
- // src/utils/queryToObject.ts
1446
- function queryToObject(params) {
1447
- const result = {};
1448
- for (const [key, value] of params) {
1449
- result[key] = value;
1302
+ // src/utils/isPlainObject.ts
1303
+ function isPlainObject(value) {
1304
+ if (value === null || typeof value !== "object") {
1305
+ return false;
1450
1306
  }
1451
- return result;
1452
- }
1453
-
1454
- // src/utils/parseJsonBody.ts
1455
- function parseJsonBody(text) {
1456
- try {
1457
- const data = JSON.parse(text);
1458
- return { success: true, data };
1459
- } catch {
1460
- return { success: false, error: "Invalid JSON body" };
1307
+ if (Array.isArray(value)) {
1308
+ return false;
1461
1309
  }
1310
+ const proto = Object.getPrototypeOf(value);
1311
+ return proto === null || proto === Object.prototype;
1462
1312
  }
1463
1313
 
1464
- // src/utils/parseMultipart.ts
1465
- async function parseMultipart(request) {
1466
- const formData = await request.formData();
1467
- const fields = {};
1468
- const files = [];
1469
- for (const [key, value] of formData.entries()) {
1470
- if (value instanceof File) {
1471
- files.push({
1472
- name: key,
1473
- filename: value.name,
1474
- type: value.type,
1475
- size: value.size,
1476
- arrayBuffer: () => value.arrayBuffer()
1477
- });
1478
- } else {
1479
- if (key in fields) {
1480
- const existing = fields[key];
1481
- if (Array.isArray(existing)) {
1482
- existing.push(value);
1483
- } else {
1484
- fields[key] = [existing, value];
1485
- }
1486
- } else {
1487
- fields[key] = value;
1488
- }
1489
- }
1490
- }
1491
- return { fields, files };
1492
- }
1493
-
1494
- // src/runtime/resolveInput.ts
1495
- async function resolveInput(method, request) {
1496
- const inputType = getInputTypeForMethod(method);
1497
- if (inputType === "body") {
1498
- const contentType = request.headers.get("content-type") ?? "";
1499
- if (contentType.includes("multipart/form-data")) {
1500
- return parseMultipart(request);
1501
- }
1502
- if (contentType.includes("application/x-www-form-urlencoded")) {
1503
- const text2 = await request.text();
1504
- if (text2.trim() === "") return null;
1505
- const params = new URLSearchParams(text2);
1506
- const obj = {};
1507
- for (const [key, value] of params) {
1508
- obj[key] = value;
1509
- }
1510
- return obj;
1511
- }
1512
- const text = await request.text();
1513
- if (text.trim() === "") {
1514
- return null;
1515
- }
1516
- const result = parseJsonBody(text);
1517
- if (!result.success) {
1518
- throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
1519
- {
1520
- path: "body",
1521
- code: "INVALID_FORMAT",
1522
- expected: "JSON",
1523
- received: "text",
1524
- message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
1525
- }
1526
- ]);
1527
- }
1528
- return result.data;
1529
- }
1530
- const url = new URL(request.url);
1531
- return queryToObject(url.searchParams);
1532
- }
1533
-
1534
- // src/utils/isPlainObject.ts
1535
- function isPlainObject(value) {
1536
- if (value === null || typeof value !== "object") {
1537
- return false;
1538
- }
1539
- if (Array.isArray(value)) {
1540
- return false;
1541
- }
1542
- const proto = Object.getPrototypeOf(value);
1543
- return proto === null || proto === Object.prototype;
1544
- }
1545
-
1546
- // src/response/toResponse.ts
1547
- async function toResponse(value, meta) {
1548
- if (value instanceof Promise) {
1549
- return toResponse(await value, meta);
1314
+ // src/response/toResponse.ts
1315
+ async function toResponse(value, meta) {
1316
+ if (value instanceof Promise) {
1317
+ return toResponse(await value, meta);
1550
1318
  }
1551
1319
  const applyMeta = (headers2) => {
1552
1320
  if (!meta) return;
@@ -1632,6 +1400,15 @@ async function toResponse(value, meta) {
1632
1400
  });
1633
1401
  }
1634
1402
 
1403
+ // src/utils/queryToObject.ts
1404
+ function queryToObject(params) {
1405
+ const result = {};
1406
+ for (const [key, value] of params) {
1407
+ result[key] = value;
1408
+ }
1409
+ return result;
1410
+ }
1411
+
1635
1412
  // src/injection/injectParams.ts
1636
1413
  function getBuiltinInjectionValue(type, ctx, body) {
1637
1414
  switch (type) {
@@ -1649,6 +1426,10 @@ function getBuiltinInjectionValue(type, ctx, body) {
1649
1426
  return ctx.ip;
1650
1427
  case "body":
1651
1428
  return body;
1429
+ // form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
1430
+ // 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
1431
+ case "form":
1432
+ return body;
1652
1433
  case "files":
1653
1434
  if (body && typeof body === "object" && "files" in body) {
1654
1435
  return body.files;
@@ -1767,6 +1548,262 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
1767
1548
  return await compose(middlewares, ctx, finalHandler);
1768
1549
  }
1769
1550
 
1551
+ // src/cli/createAppCore.ts
1552
+ import fs4 from "fs";
1553
+ import path7 from "path";
1554
+ import { PassThrough } from "stream";
1555
+
1556
+ // src/router/sortRoutes.ts
1557
+ function sortRoutes(routes) {
1558
+ return [...routes].sort((a, b) => {
1559
+ if (a.isDynamic !== b.isDynamic) {
1560
+ return a.isDynamic ? 1 : -1;
1561
+ }
1562
+ if (a.isCatchAll !== b.isCatchAll) {
1563
+ return a.isCatchAll ? 1 : -1;
1564
+ }
1565
+ const aSegments = a.urlPath.split("/").filter(Boolean).length;
1566
+ const bSegments = b.urlPath.split("/").filter(Boolean).length;
1567
+ if (aSegments !== bSegments) {
1568
+ return aSegments - bSegments;
1569
+ }
1570
+ return a.urlPath.localeCompare(b.urlPath);
1571
+ });
1572
+ }
1573
+
1574
+ // src/router/detectRouteConflicts.ts
1575
+ function detectRouteConflicts(routes) {
1576
+ const map = /* @__PURE__ */ new Map();
1577
+ for (const route of routes) {
1578
+ const key = `${route.method} ${route.urlPath}`;
1579
+ const existing = map.get(key);
1580
+ if (existing) {
1581
+ existing.files.push(route.filePath);
1582
+ } else {
1583
+ map.set(key, {
1584
+ method: route.method,
1585
+ urlPath: route.urlPath,
1586
+ files: [route.filePath]
1587
+ });
1588
+ }
1589
+ }
1590
+ const conflicts = [];
1591
+ for (const conflict of map.values()) {
1592
+ if (conflict.files.length > 1) {
1593
+ conflicts.push(conflict);
1594
+ }
1595
+ }
1596
+ return conflicts;
1597
+ }
1598
+
1599
+ // src/server/createServer.ts
1600
+ import {
1601
+ createServer as createHttpServer
1602
+ } from "http";
1603
+ import { createSecureServer as createHttp2SecureServer } from "http2";
1604
+ import { readFileSync } from "fs";
1605
+ import { Readable as Readable2 } from "stream";
1606
+ import path5 from "path";
1607
+
1608
+ // src/router/matchRoute.ts
1609
+ function matchRoute(routes, method, path9) {
1610
+ for (const route of routes) {
1611
+ if (route.method !== method) {
1612
+ continue;
1613
+ }
1614
+ if (!route.isDynamic) {
1615
+ if (route.urlPath === path9) {
1616
+ return { route, params: {} };
1617
+ }
1618
+ continue;
1619
+ }
1620
+ const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1621
+ if (params !== null) {
1622
+ return { route, params };
1623
+ }
1624
+ }
1625
+ return null;
1626
+ }
1627
+ function matchWsRoute(wsRoutes, path9) {
1628
+ for (const route of wsRoutes) {
1629
+ if (!route.isDynamic) {
1630
+ if (route.urlPath === path9) {
1631
+ return { route, params: {} };
1632
+ }
1633
+ continue;
1634
+ }
1635
+ const params = matchDynamicPath(route.urlPath, path9, route.paramNames, route.isCatchAll);
1636
+ if (params !== null) {
1637
+ return { route, params };
1638
+ }
1639
+ }
1640
+ return null;
1641
+ }
1642
+ function matchDynamicPath(pattern, path9, paramNames, isCatchAll) {
1643
+ const patternSegments = pattern.split("/").filter(Boolean);
1644
+ const pathSegments = path9.split("/").filter(Boolean);
1645
+ if (isCatchAll) {
1646
+ const nonCatchAllCount = patternSegments.length - 1;
1647
+ if (pathSegments.length <= nonCatchAllCount) {
1648
+ return null;
1649
+ }
1650
+ const params2 = {};
1651
+ for (let i = 0; i < nonCatchAllCount; i++) {
1652
+ const patternSeg = patternSegments[i];
1653
+ const pathSeg = pathSegments[i];
1654
+ if (patternSeg.startsWith(":")) {
1655
+ const paramName = patternSeg.slice(1);
1656
+ params2[paramName] = pathSeg;
1657
+ } else if (patternSeg !== pathSeg) {
1658
+ return null;
1659
+ }
1660
+ }
1661
+ const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
1662
+ const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
1663
+ params2[catchAllParamName] = catchAllValue;
1664
+ if (Object.keys(params2).length !== paramNames.length) {
1665
+ return null;
1666
+ }
1667
+ return params2;
1668
+ }
1669
+ if (patternSegments.length !== pathSegments.length) {
1670
+ return null;
1671
+ }
1672
+ const params = {};
1673
+ for (let i = 0; i < patternSegments.length; i++) {
1674
+ const patternSeg = patternSegments[i];
1675
+ const pathSeg = pathSegments[i];
1676
+ if (patternSeg.startsWith(":")) {
1677
+ const paramName = patternSeg.slice(1);
1678
+ params[paramName] = pathSeg;
1679
+ } else if (patternSeg !== pathSeg) {
1680
+ return null;
1681
+ }
1682
+ }
1683
+ if (Object.keys(params).length !== paramNames.length) {
1684
+ return null;
1685
+ }
1686
+ return params;
1687
+ }
1688
+
1689
+ // src/loader/resolveExports.ts
1690
+ function resolveExport(module, exportName) {
1691
+ if (exportName in module && typeof module[exportName] !== "undefined") {
1692
+ return module[exportName];
1693
+ }
1694
+ const defaultExport = module.default;
1695
+ if (defaultExport !== null && typeof defaultExport === "object") {
1696
+ const value = defaultExport[exportName];
1697
+ if (value !== void 0) {
1698
+ return value;
1699
+ }
1700
+ }
1701
+ return void 0;
1702
+ }
1703
+
1704
+ // src/loader/validateRouteModule.ts
1705
+ function validateRouteModule(value, method, filePath) {
1706
+ if (typeof value !== "function") {
1707
+ throw new Error(
1708
+ `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
1709
+ );
1710
+ }
1711
+ }
1712
+
1713
+ // src/loader/loadRouteModule.ts
1714
+ async function loadRouteModule(filePath, method) {
1715
+ let module;
1716
+ try {
1717
+ module = await importWithCacheBust(filePath);
1718
+ } catch (err) {
1719
+ const reason = err instanceof Error ? err.message : String(err);
1720
+ throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
1721
+ }
1722
+ const handler = resolveExport(module, method);
1723
+ validateRouteModule(handler, method, filePath);
1724
+ return { handler, method };
1725
+ }
1726
+
1727
+ // src/utils/parseJsonBody.ts
1728
+ function parseJsonBody(text) {
1729
+ try {
1730
+ const data = JSON.parse(text);
1731
+ return { success: true, data };
1732
+ } catch {
1733
+ return { success: false, error: "Invalid JSON body" };
1734
+ }
1735
+ }
1736
+
1737
+ // src/utils/parseMultipart.ts
1738
+ async function parseMultipart(request) {
1739
+ const formData = await request.formData();
1740
+ const fields = {};
1741
+ const files = [];
1742
+ for (const [key, value] of formData.entries()) {
1743
+ if (value instanceof File) {
1744
+ files.push({
1745
+ name: key,
1746
+ filename: value.name,
1747
+ type: value.type,
1748
+ size: value.size,
1749
+ arrayBuffer: () => value.arrayBuffer()
1750
+ });
1751
+ } else {
1752
+ if (key in fields) {
1753
+ const existing = fields[key];
1754
+ if (Array.isArray(existing)) {
1755
+ existing.push(value);
1756
+ } else {
1757
+ fields[key] = [existing, value];
1758
+ }
1759
+ } else {
1760
+ fields[key] = value;
1761
+ }
1762
+ }
1763
+ }
1764
+ return { fields, files };
1765
+ }
1766
+
1767
+ // src/runtime/resolveInput.ts
1768
+ async function resolveInput(method, request) {
1769
+ const inputType = getInputTypeForMethod(method);
1770
+ if (inputType === "body") {
1771
+ const contentType = request.headers.get("content-type") ?? "";
1772
+ if (contentType.includes("multipart/form-data")) {
1773
+ return parseMultipart(request);
1774
+ }
1775
+ if (contentType.includes("application/x-www-form-urlencoded")) {
1776
+ const text2 = await request.text();
1777
+ if (text2.trim() === "") return null;
1778
+ const params = new URLSearchParams(text2);
1779
+ const obj = {};
1780
+ for (const [key, value] of params) {
1781
+ obj[key] = value;
1782
+ }
1783
+ return obj;
1784
+ }
1785
+ const text = await request.text();
1786
+ if (text.trim() === "") {
1787
+ return null;
1788
+ }
1789
+ const result = parseJsonBody(text);
1790
+ if (!result.success) {
1791
+ throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
1792
+ {
1793
+ path: "body",
1794
+ code: "INVALID_FORMAT",
1795
+ expected: "JSON",
1796
+ received: "text",
1797
+ message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
1798
+ }
1799
+ ]);
1800
+ }
1801
+ return result.data;
1802
+ }
1803
+ const url = new URL(request.url);
1804
+ return queryToObject(url.searchParams);
1805
+ }
1806
+
1770
1807
  // src/response/sendNodeResponse.ts
1771
1808
  import { Readable } from "stream";
1772
1809
  async function sendNodeResponse(response, res) {
@@ -2175,6 +2212,13 @@ function collectNamedTypes(type, ctx) {
2175
2212
  collectNamedTypes(type.key, ctx);
2176
2213
  collectNamedTypes(type.value, ctx);
2177
2214
  return;
2215
+ case "map":
2216
+ collectNamedTypes(type.key, ctx);
2217
+ collectNamedTypes(type.value, ctx);
2218
+ return;
2219
+ case "set":
2220
+ collectNamedTypes(type.element, ctx);
2221
+ return;
2178
2222
  case "ref": {
2179
2223
  if (ctx.namedTypes.has(type.name)) return;
2180
2224
  ctx.namedTypes.set(type.name, { kind: "any" });
@@ -2264,6 +2308,10 @@ function baseExpression(type, ctx) {
2264
2308
  return 'z.preprocess((v) => (typeof v === "string" ? new Date(v) : v), z.date())';
2265
2309
  case "record":
2266
2310
  return `z.record(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)})`;
2311
+ case "map":
2312
+ return `z.preprocess(coerceMap, z.map(${runtimeTypeToZodExpression(type.key, ctx)}, ${runtimeTypeToZodExpression(type.value, ctx)}))`;
2313
+ case "set":
2314
+ return `z.preprocess(coerceSet, z.set(${runtimeTypeToZodExpression(type.element, ctx)}))`;
2267
2315
  case "ref":
2268
2316
  if (type.name === ctx.entryTypeName) {
2269
2317
  return `${ctx.entryExportName}Schema`;
@@ -2273,17 +2321,21 @@ function baseExpression(type, ctx) {
2273
2321
  }
2274
2322
  var COERCE_NUMBER_HELPER = 'export const coerceNumber = (v) => typeof v === "string" && v.trim() !== "" && !isNaN(Number(v)) ? Number(v) : v;';
2275
2323
  var COERCE_BOOLEAN_HELPER = 'export const coerceBoolean = (v) => v === "true" || v === "1" ? true : v === "false" || v === "0" ? false : v;';
2324
+ var COERCE_MAP_HELPER = 'export const coerceMap = (v) => Array.isArray(v) ? new Map(v) : v instanceof Map ? v : (v && typeof v === "object" ? new Map(Object.entries(v)) : v);';
2325
+ var COERCE_SET_HELPER = "export const coerceSet = (v) => v instanceof Set ? v : (Array.isArray(v) ? new Set(v) : v);";
2276
2326
  var HELPERS_FILENAME = "faapi-helpers.js";
2277
2327
  function generateHelpersFileSource() {
2278
2328
  return [
2279
2329
  "// faapi-helpers.js \u2014 faapi \u81EA\u52A8\u751F\u6210\u7684\u516C\u7528\u51FD\u6570\uFF08\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91\uFF09",
2280
2330
  COERCE_NUMBER_HELPER,
2281
2331
  COERCE_BOOLEAN_HELPER,
2332
+ COERCE_MAP_HELPER,
2333
+ COERCE_SET_HELPER,
2282
2334
  ""
2283
2335
  ].join("\n");
2284
2336
  }
2285
2337
  function usesCoerceHelpers(code) {
2286
- return code.includes("coerceNumber") || code.includes("coerceBoolean");
2338
+ return code.includes("coerceNumber") || code.includes("coerceBoolean") || code.includes("coerceMap") || code.includes("coerceSet");
2287
2339
  }
2288
2340
  function wrapCoercePreprocess(kind, inner) {
2289
2341
  if (kind === "number") {
@@ -2370,6 +2422,10 @@ function containsRef(type, visited) {
2370
2422
  return type.members.some((m) => containsRef(m, visited));
2371
2423
  case "record":
2372
2424
  return containsRef(type.key, visited) || containsRef(type.value, visited);
2425
+ case "map":
2426
+ return containsRef(type.key, visited) || containsRef(type.value, visited);
2427
+ case "set":
2428
+ return containsRef(type.element, visited);
2373
2429
  default:
2374
2430
  return false;
2375
2431
  }
@@ -2400,25 +2456,25 @@ function generateZodSchemaSource(typeInfo, resolveType, exportName, coerce = fal
2400
2456
  }
2401
2457
 
2402
2458
  // src/cli/generateSchemaFiles.ts
2403
- function getSchemaOutputPath(sourceFile, appDir, outDir, rootDir) {
2459
+ function getSchemaOutputPath(sourceFile, dist, rootDir) {
2404
2460
  let rel = sourceFile.replace(/\\/g, "/");
2405
- if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
2406
- rel = rel.slice(appDir.length + 1);
2461
+ if (rel.startsWith("src/")) {
2462
+ rel = rel.slice(4);
2407
2463
  }
2408
2464
  const idx = rel.lastIndexOf("/");
2409
2465
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2410
- return path4.resolve(rootDir, outDir, relDir, "zod.js");
2466
+ return path4.resolve(rootDir, dist, relDir, "zod.js");
2411
2467
  }
2412
- function getRuntimeSchemaPath(filePath, appDir, outDir, rootDir) {
2468
+ function getRuntimeSchemaPath(filePath, dist, rootDir) {
2413
2469
  let rel = filePath.replace(/\\/g, "/");
2414
- if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
2415
- rel = rel.slice(appDir.length + 1);
2416
- } else if (rel.startsWith(`${outDir}/`)) {
2417
- rel = rel.slice(outDir.length + 1);
2470
+ if (rel.startsWith("src/")) {
2471
+ rel = rel.slice(4);
2472
+ } else if (rel.startsWith(`${dist}/`)) {
2473
+ rel = rel.slice(dist.length + 1);
2418
2474
  }
2419
2475
  const idx = rel.lastIndexOf("/");
2420
2476
  const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2421
- return path4.resolve(rootDir, outDir, relDir, "zod.js");
2477
+ return path4.resolve(rootDir, dist, relDir, "zod.js");
2422
2478
  }
2423
2479
  function getHelpersImportPath(relDir) {
2424
2480
  if (!relDir) return `./${HELPERS_FILENAME}`;
@@ -2434,7 +2490,7 @@ function generateSchemaFileSource(sources, allTypes, helpersImportPath) {
2434
2490
  if (!typeInfo) {
2435
2491
  continue;
2436
2492
  }
2437
- const coerce = /(?:Query|Params)$/.test(schemaName);
2493
+ const coerce = source.coerce ?? /(?:Query|Params)$/.test(schemaName);
2438
2494
  const block = [`// ${schemaName}`];
2439
2495
  const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(
2440
2496
  /^import \{ z \} from 'zod';\s*\n\s*\n/,
@@ -2446,13 +2502,15 @@ function generateSchemaFileSource(sources, allTypes, helpersImportPath) {
2446
2502
  }
2447
2503
  const allSchemaCode = schemaBlocks.join("\n");
2448
2504
  if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
2449
- lines.push(`import { coerceNumber, coerceBoolean } from '${helpersImportPath}';`);
2505
+ lines.push(
2506
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
2507
+ );
2450
2508
  }
2451
2509
  lines.push("");
2452
2510
  lines.push(...schemaBlocks);
2453
2511
  return lines.join("\n").replace(/\n+$/, "\n");
2454
2512
  }
2455
- async function generateSchemaFiles(routes, rootDir, appDir, outDir) {
2513
+ async function generateSchemaFiles(routes, rootDir, dist) {
2456
2514
  if (routes.length === 0) return;
2457
2515
  const { sources, allTypesByFile } = collectRouteSchemaSources(routes, rootDir);
2458
2516
  const sourcesByFile = /* @__PURE__ */ new Map();
@@ -2467,11 +2525,11 @@ async function generateSchemaFiles(routes, rootDir, appDir, outDir) {
2467
2525
  const fileEntries = [];
2468
2526
  for (const [filePath, fileSources] of sourcesByFile) {
2469
2527
  const relFile = path4.relative(rootDir, filePath).replace(/\\/g, "/");
2470
- const outputPath = getSchemaOutputPath(relFile, appDir, outDir, rootDir);
2528
+ const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
2471
2529
  const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
2472
2530
  let relForDir = relFile;
2473
- if (appDir !== "." && relForDir.startsWith(`${appDir}/`)) {
2474
- relForDir = relForDir.slice(appDir.length + 1);
2531
+ if (relForDir.startsWith("src/")) {
2532
+ relForDir = relForDir.slice(4);
2475
2533
  }
2476
2534
  const dirIdx = relForDir.lastIndexOf("/");
2477
2535
  const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
@@ -2481,7 +2539,7 @@ async function generateSchemaFiles(routes, rootDir, appDir, outDir) {
2481
2539
  }
2482
2540
  const allSourceCode = fileEntries.map((e) => e.source).join("\n");
2483
2541
  if (usesCoerceHelpers(allSourceCode)) {
2484
- const helpersPath = path4.resolve(rootDir, outDir, HELPERS_FILENAME);
2542
+ const helpersPath = path4.resolve(rootDir, dist, HELPERS_FILENAME);
2485
2543
  await writeSchemaFile(helpersPath, generateHelpersFileSource());
2486
2544
  }
2487
2545
  await Promise.all(
@@ -2558,8 +2616,7 @@ function createServer(options) {
2558
2616
  const {
2559
2617
  routes,
2560
2618
  rootDir,
2561
- appDir,
2562
- outDir,
2619
+ dist,
2563
2620
  cors: corsOption,
2564
2621
  onError,
2565
2622
  config,
@@ -2567,6 +2624,7 @@ function createServer(options) {
2567
2624
  middlewares: globalMiddlewares,
2568
2625
  injectors: globalInjectors,
2569
2626
  helmet: helmetOption,
2627
+ logger: loggerOption,
2570
2628
  bodyLimit = DEFAULT_BODY_LIMIT,
2571
2629
  http2: http2Option
2572
2630
  } = options;
@@ -2578,6 +2636,8 @@ function createServer(options) {
2578
2636
  const helmOpts = typeof helmetOption === "object" ? helmetOption : {};
2579
2637
  configMiddlewares.push(helmet(helmOpts));
2580
2638
  }
2639
+ const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
2640
+ if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
2581
2641
  const server = (() => {
2582
2642
  if (http2Option) {
2583
2643
  const h2Opts = typeof http2Option === "object" ? http2Option : {};
@@ -2594,8 +2654,7 @@ function createServer(options) {
2594
2654
  handleRequest(
2595
2655
  currentRoutes,
2596
2656
  rootDir,
2597
- appDir,
2598
- outDir,
2657
+ dist,
2599
2658
  req,
2600
2659
  res,
2601
2660
  configMiddlewares,
@@ -2614,7 +2673,7 @@ function createServer(options) {
2614
2673
  }
2615
2674
  return { server, routesRef };
2616
2675
  }
2617
- async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
2676
+ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
2618
2677
  const request = toWebRequest(req, bodyLimit);
2619
2678
  const method = request.method.toUpperCase();
2620
2679
  const urlPath = new URL(request.url).pathname;
@@ -2635,7 +2694,7 @@ async function handleRequest(routes, rootDir, appDir, outDir, req, res, configMi
2635
2694
  const routeModule = await loadRouteModule(absoluteFilePath, route.method);
2636
2695
  const input = await resolveInput(route.method, request);
2637
2696
  const inputType = getInputTypeForMethod(route.method);
2638
- const schemaPath = getRuntimeSchemaPath(route.filePath, appDir, outDir, rootDir);
2697
+ const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
2639
2698
  const result = await validateInput(schemaPath, route.method, inputType, input);
2640
2699
  if (!result.valid) {
2641
2700
  throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
@@ -2862,10 +2921,10 @@ function resolveDeclaration(decl) {
2862
2921
  }
2863
2922
 
2864
2923
  // src/cli/createAppCore.ts
2865
- var DEFAULT_OUT_DIR = "dist";
2866
- var DEFAULT_APP_DIR = "src";
2924
+ var DEFAULT_DIST = "dist";
2867
2925
  var DEFAULT_PORT = 3e3;
2868
2926
  var ROUTES_FILE = "faapi-routes.js";
2927
+ var PATTERNS = ["src/api/**/*.ts"];
2869
2928
  var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
2870
2929
  "cors",
2871
2930
  "lifecycle",
@@ -2883,16 +2942,14 @@ function isFaapiConfigKey(key) {
2883
2942
  }
2884
2943
  async function createAppBase(options) {
2885
2944
  const rootDir = options?.rootDir ?? process.cwd();
2886
- const outDir = process.env.FAAPI_OUT_DIR ?? DEFAULT_OUT_DIR;
2887
- const routesPath = path7.resolve(rootDir, outDir, ROUTES_FILE);
2945
+ const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
2946
+ const routesPath = path7.resolve(rootDir, dist, ROUTES_FILE);
2888
2947
  if (!fs4.existsSync(routesPath)) {
2889
2948
  throw new Error(
2890
- `[faapi] ${outDir}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
2949
+ `[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
2891
2950
  );
2892
2951
  }
2893
- const config = await loadConfig(rootDir, outDir);
2894
- const appDir = options?.appDir ?? process.env.FAAPI_APP_DIR ?? DEFAULT_APP_DIR;
2895
- const patterns = appDir === "." ? ["api/**/*.ts"] : [`${appDir}/api/**/*.ts`];
2952
+ const config = await loadConfig(rootDir, dist);
2896
2953
  const serialized = await importWithCacheBust(routesPath);
2897
2954
  const hydrated = await hydrateRoutes(serialized);
2898
2955
  let sorted = sortRoutes(hydrated.routes);
@@ -2910,8 +2967,7 @@ async function createAppBase(options) {
2910
2967
  const { server, routesRef } = createServer({
2911
2968
  routes: sorted,
2912
2969
  rootDir,
2913
- appDir,
2914
- outDir,
2970
+ dist,
2915
2971
  cors: config?.cors ?? true,
2916
2972
  onError: config?.lifecycle?.onError,
2917
2973
  config: config ?? void 0,
@@ -2919,6 +2975,7 @@ async function createAppBase(options) {
2919
2975
  middlewares: config?.middlewares,
2920
2976
  injectors: config?.injectors,
2921
2977
  helmet: config?.helmet,
2978
+ logger: config?.logger,
2922
2979
  bodyLimit: config?.bodyLimit,
2923
2980
  http2: config?.http2
2924
2981
  });
@@ -3071,9 +3128,8 @@ async function createAppBase(options) {
3071
3128
  };
3072
3129
  const ctx = {
3073
3130
  rootDir,
3074
- appDir,
3075
- outDir,
3076
- patterns,
3131
+ dist,
3132
+ patterns: PATTERNS,
3077
3133
  server,
3078
3134
  routesRef,
3079
3135
  config,
@@ -3139,8 +3195,8 @@ function isCatchAllSegment(segment) {
3139
3195
  function isRouteGroup(segment) {
3140
3196
  return /^\(.+\)$/.test(segment);
3141
3197
  }
3142
- function filePathToUrlPath(filePath, appDir = ".") {
3143
- const withoutPrefix = filePath.startsWith(appDir + "/") ? filePath.slice(appDir.length + 1) : filePath;
3198
+ function filePathToUrlPath(filePath) {
3199
+ const withoutPrefix = filePath.startsWith("src/") ? filePath.slice(4) : filePath;
3144
3200
  const lastSlashIndex = withoutPrefix.lastIndexOf("/");
3145
3201
  const dirPath = lastSlashIndex === -1 ? "" : withoutPrefix.slice(0, lastSlashIndex);
3146
3202
  if (!dirPath) {
@@ -3151,24 +3207,25 @@ function filePathToUrlPath(filePath, appDir = ".") {
3151
3207
  }
3152
3208
 
3153
3209
  // src/router/scanRoutes.ts
3154
- function toProdAbsPath(sourceAbsPath, rootDir, appDir, prodDir) {
3210
+ var APP_DIR = "src";
3211
+ function toProdAbsPath(sourceAbsPath, rootDir, dist) {
3155
3212
  let rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
3156
- if (appDir !== "." && rel.startsWith(`${appDir}/`)) {
3157
- rel = rel.slice(appDir.length + 1);
3213
+ if (rel.startsWith(`${APP_DIR}/`)) {
3214
+ rel = rel.slice(APP_DIR.length + 1);
3158
3215
  }
3159
- const prodRel = `${prodDir}/${rel.replace(/\.ts$/, ".js")}`;
3216
+ const prodRel = `${dist}/${rel.replace(/\.ts$/, ".js")}`;
3160
3217
  return path8.resolve(rootDir, prodRel);
3161
3218
  }
3162
- async function findMergedMiddlewares(routeFilePath, rootDir, appDir, prodDir) {
3219
+ async function findMergedMiddlewares(routeFilePath, rootDir, dist) {
3163
3220
  const routeDir = path8.dirname(routeFilePath);
3164
3221
  const resolvedRoot = path8.resolve(rootDir);
3165
3222
  const mwPaths = [];
3166
3223
  let currentDir = path8.resolve(rootDir, routeDir);
3167
3224
  while (true) {
3168
- if (prodDir) {
3225
+ if (dist) {
3169
3226
  const mwPath = path8.join(currentDir, "middlewares.js");
3170
3227
  const absMwPath = path8.resolve(rootDir, mwPath);
3171
- const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, appDir, prodDir);
3228
+ const prodAbsMwPath = toProdAbsPath(absMwPath, rootDir, dist);
3172
3229
  if (fs5.existsSync(prodAbsMwPath)) {
3173
3230
  mwPaths.push(prodAbsMwPath);
3174
3231
  }
@@ -3233,8 +3290,7 @@ async function hasWsExport(absPath) {
3233
3290
  return false;
3234
3291
  }
3235
3292
  }
3236
- async function scanRoutes(rootDir, patterns, appDir, prodDir) {
3237
- const dir = appDir ?? ".";
3293
+ async function scanRoutes(rootDir, patterns, dist) {
3238
3294
  const files = await fg(patterns, {
3239
3295
  cwd: rootDir,
3240
3296
  onlyFiles: true,
@@ -3247,12 +3303,12 @@ async function scanRoutes(rootDir, patterns, appDir, prodDir) {
3247
3303
  const fileName = normalizedFile.split("/").pop();
3248
3304
  if (fileName === "handler.ts" || fileName === "handler.js") {
3249
3305
  const absPath = path8.resolve(rootDir, normalizedFile);
3250
- const importPath = prodDir ? toProdAbsPath(absPath, rootDir, dir, prodDir) : absPath;
3251
- const urlPath = filePathToUrlPath(normalizedFile, dir);
3306
+ const importPath = dist ? toProdAbsPath(absPath, rootDir, dist) : absPath;
3307
+ const urlPath = filePathToUrlPath(normalizedFile);
3252
3308
  const paramNames = extractParamNames(urlPath);
3253
3309
  const isDynamic = paramNames.length > 0;
3254
3310
  const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
3255
- const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dir, prodDir);
3311
+ const middlewareBundle = await findMergedMiddlewares(normalizedFile, rootDir, dist);
3256
3312
  const methods = await extractMethodsFromHandler(importPath);
3257
3313
  for (const method of methods) {
3258
3314
  routes.push({
@@ -3293,9 +3349,9 @@ async function createDevApp(options) {
3293
3349
  invalidateMiddlewareCache();
3294
3350
  invalidateProgramCache();
3295
3351
  invalidateSchemaCache();
3296
- const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.appDir, ctx.outDir);
3352
+ const reScanned = await scanRoutes(ctx.rootDir, ctx.patterns, ctx.dist);
3297
3353
  const sorted = sortRoutes(reScanned.routes);
3298
- await generateSchemaFiles(sorted, ctx.rootDir, ctx.appDir, ctx.outDir);
3354
+ await generateSchemaFiles(sorted, ctx.rootDir, ctx.dist);
3299
3355
  ctx.updateRoutes(sorted, reScanned.wsRoutes);
3300
3356
  };
3301
3357
  return devApp;
@@ -3317,6 +3373,7 @@ export {
3317
3373
  collectRouteSchemaSources,
3318
3374
  cors,
3319
3375
  createProdApp as createApp,
3376
+ createContext,
3320
3377
  createDevApp,
3321
3378
  createProdApp,
3322
3379
  createProgram,
@@ -3324,6 +3381,7 @@ export {
3324
3381
  getInputTypeForMethod,
3325
3382
  helmet,
3326
3383
  invalidateProgramCache,
3384
+ invokeHandler,
3327
3385
  loadConfig,
3328
3386
  logger,
3329
3387
  resolveTypeNode