koatty_router 2.1.9 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -54,7 +54,7 @@ var onFinished__default = /*#__PURE__*/_interopDefault(onFinished);
54
54
 
55
55
  /*!
56
56
  * @Author: richen
57
- * @Date: 2026-03-07 15:34:44
57
+ * @Date: 2026-04-24 08:20:32
58
58
  * @License: BSD (3-Clause)
59
59
  * @Copyright (c) - <richenlin(at)gmail.com>
60
60
  * @HomePage: https://koatty.org/
@@ -191,9 +191,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
191
191
  headerCache = new lruCache.LRUCache({
192
192
  max: 100
193
193
  });
194
- // 缓存清理定时器
195
- cacheCleanupTimer;
196
- CACHE_CLEANUP_INTERVAL = 5 * 60 * 1e3;
197
194
  /**
198
195
  * Private constructor to enforce singleton pattern
199
196
  */
@@ -204,7 +201,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
204
201
  }
205
202
  this._instanceId = Math.random().toString(36).substr(2, 9);
206
203
  koatty_logger.DefaultLogger.Debug(`RouterMiddlewareManager instance created with ID: ${this._instanceId}`);
207
- this.startCacheCleanup();
208
204
  }
209
205
  /**
210
206
  * Get singleton instance
@@ -238,22 +234,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
238
234
  koatty_logger.DefaultLogger.Debug("RouterMiddlewareManager singleton instance reset");
239
235
  }
240
236
  /**
241
- * Start cache cleanup timer
242
- */
243
- startCacheCleanup() {
244
- this.cacheCleanupTimer = setInterval(() => {
245
- this.performCacheCleanup();
246
- }, this.CACHE_CLEANUP_INTERVAL);
247
- }
248
- /**
249
- * Perform periodic cache cleanup
250
- */
251
- performCacheCleanup() {
252
- const beforeSize = this.getCacheSize();
253
- const afterSize = this.getCacheSize();
254
- koatty_logger.DefaultLogger.Debug(`Cache cleanup completed. Size: ${beforeSize} -> ${afterSize}`);
255
- }
256
- /**
257
237
  * Get total cache size
258
238
  */
259
239
  getCacheSize() {
@@ -263,10 +243,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
263
243
  * Destroy manager and cleanup resources
264
244
  */
265
245
  destroy() {
266
- if (this.cacheCleanupTimer) {
267
- clearInterval(this.cacheCleanupTimer);
268
- this.cacheCleanupTimer = void 0;
269
- }
270
246
  this.clearCaches();
271
247
  this.middlewares.clear();
272
248
  koatty_logger.DefaultLogger.Debug(`RouterMiddlewareManager instance ${this._instanceId} destroyed`);
@@ -740,9 +716,9 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
740
716
  /**
741
717
  * Create middleware group
742
718
  */
743
- createGroup(groupName, middlewareNames) {
719
+ async createGroup(groupName, middlewareNames) {
744
720
  const groupMiddleware = this.compose(middlewareNames);
745
- this.register({
721
+ await this.register({
746
722
  name: groupName,
747
723
  middleware: groupMiddleware,
748
724
  metadata: {
@@ -813,6 +789,9 @@ function RegisterMiddleware(app, config) {
813
789
  };
814
790
  }
815
791
  __name(RegisterMiddleware, "RegisterMiddleware");
792
+
793
+ // src/payload/interface.ts
794
+ var FILE_KEY = /* @__PURE__ */ Symbol.for("koatty.files");
816
795
  function parseText(ctx, opts) {
817
796
  return getRawBody__default.default(inflate__default.default(ctx.req), opts).catch((err) => {
818
797
  koatty_logger.DefaultLogger.Error(err);
@@ -824,8 +803,9 @@ async function parseJson(ctx, opts) {
824
803
  const str = await parseText(ctx, opts);
825
804
  if (!str) return {};
826
805
  try {
827
- return {
828
- body: JSON.parse(str)
806
+ const parsed = JSON.parse(str);
807
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
808
+ value: parsed
829
809
  };
830
810
  } catch (error) {
831
811
  koatty_logger.DefaultLogger.Error(error);
@@ -834,18 +814,13 @@ async function parseJson(ctx, opts) {
834
814
  }
835
815
  __name(parseJson, "parseJson");
836
816
  async function parseForm(ctx, opts) {
837
- if (!ctx.request.headers["content-length"] || !ctx.request.headers["content-type"]?.includes("application/x-www-form-urlencoded")) {
838
- return {};
839
- }
840
817
  const str = await parseText(ctx, opts);
841
818
  if (!str || str.trim().length === 0) {
842
819
  return {};
843
820
  }
844
821
  try {
845
822
  const result = fastQuerystring.parse(str);
846
- return {
847
- body: result
848
- };
823
+ return result;
849
824
  } catch (error) {
850
825
  koatty_logger.DefaultLogger.Error("[FormParseError]", error);
851
826
  return {};
@@ -877,12 +852,6 @@ __name(deleteFiles, "deleteFiles");
877
852
 
878
853
  // src/payload/parser/multipart.ts
879
854
  function parseMultipart(ctx, opts) {
880
- if (!ctx.request.headers["content-type"]?.includes("multipart/form-data")) {
881
- return Promise.resolve({
882
- body: {},
883
- file: {}
884
- });
885
- }
886
855
  const form = new formidable.IncomingForm({
887
856
  encoding: opts.encoding,
888
857
  multiples: opts.multiples,
@@ -905,15 +874,12 @@ function parseMultipart(ctx, opts) {
905
874
  if (err) {
906
875
  cleanup();
907
876
  koatty_logger.DefaultLogger.Error("[MultipartParseError]", err);
908
- return resolve({
909
- body: {},
910
- file: {}
911
- });
877
+ return resolve({});
912
878
  }
913
879
  uploadFiles = files;
914
880
  resolve({
915
- body: fields,
916
- file: files
881
+ ...fields,
882
+ [FILE_KEY]: files
917
883
  });
918
884
  });
919
885
  });
@@ -927,8 +893,9 @@ async function parseXml(ctx, opts) {
927
893
  const str = await parseText(ctx, opts);
928
894
  if (!str) return {};
929
895
  try {
930
- return {
931
- body: xmlParser.parse(str)
896
+ const parsed = xmlParser.parse(str);
897
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
898
+ value: parsed
932
899
  };
933
900
  } catch (error) {
934
901
  koatty_logger.DefaultLogger.Error(error);
@@ -969,11 +936,14 @@ async function parseWebSocket(ctx, opts) {
969
936
  const str = await parseText(ctx, opts);
970
937
  if (!str) return {};
971
938
  try {
972
- return {
973
- body: JSON.parse(str)
939
+ const parsed = JSON.parse(str);
940
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
941
+ value: parsed
974
942
  };
975
943
  } catch {
976
- return str;
944
+ return {
945
+ value: str
946
+ };
977
947
  }
978
948
  } catch (error) {
979
949
  koatty_logger.DefaultLogger.Error("[WebSocketParseError]", error);
@@ -1193,6 +1163,7 @@ var PayloadCacheManager = class _PayloadCacheManager {
1193
1163
  var cacheManager = PayloadCacheManager.getInstance();
1194
1164
 
1195
1165
  // src/payload/payload.ts
1166
+ var bodyCache = /* @__PURE__ */ new WeakMap();
1196
1167
  var supportedMethods = /* @__PURE__ */ new Set([
1197
1168
  "POST",
1198
1169
  "PUT",
@@ -1250,7 +1221,7 @@ function payload(options) {
1250
1221
  if (!Object.prototype.hasOwnProperty.call(ctx, "requestFile")) {
1251
1222
  Helper5.Helper.define(ctx, "requestFile", async (name) => {
1252
1223
  const body = await bodyParser(ctx, opts);
1253
- const files = body.file ?? {};
1224
+ const files = body[FILE_KEY] ?? {};
1254
1225
  return name ? files[name] : files;
1255
1226
  });
1256
1227
  }
@@ -1270,22 +1241,32 @@ function queryParser(ctx, _options) {
1270
1241
  return Object.assign({}, query, params);
1271
1242
  }
1272
1243
  __name(queryParser, "queryParser");
1273
- async function bodyParser(ctx, options) {
1244
+ function bodyParser(ctx, options) {
1245
+ const cached = bodyCache.get(ctx);
1246
+ if (cached !== void 0) return cached;
1247
+ return parseBodyAndCache(ctx, options);
1248
+ }
1249
+ __name(bodyParser, "bodyParser");
1250
+ async function parseBodyAndCache(ctx, options) {
1274
1251
  try {
1275
- let body = ctx.getMetaData("_body")[0];
1276
- if (!Helper5.Helper.isEmpty(body)) {
1277
- return body;
1278
- }
1252
+ const cached = bodyCache.get(ctx);
1253
+ if (cached !== void 0) return cached;
1279
1254
  const opts = cacheManager.getMergedOptions(options);
1280
- body = await parseBody(ctx, opts);
1281
- ctx.setMetaData("_body", body);
1255
+ const body = await parseBody(ctx, opts);
1256
+ bodyCache.set(ctx, body);
1257
+ try {
1258
+ ctx.setMetaData("_body", body);
1259
+ } catch {
1260
+ }
1282
1261
  return body;
1283
1262
  } catch (err) {
1284
1263
  koatty_logger.DefaultLogger.Error(err);
1285
- return {};
1264
+ const empty = {};
1265
+ bodyCache.set(ctx, empty);
1266
+ return empty;
1286
1267
  }
1287
1268
  }
1288
- __name(bodyParser, "bodyParser");
1269
+ __name(parseBodyAndCache, "parseBodyAndCache");
1289
1270
  function parseBody(ctx, options) {
1290
1271
  if (!isSupportedMethod(ctx.method)) {
1291
1272
  return Promise.resolve({});
@@ -1303,6 +1284,8 @@ function parseBody(ctx, options) {
1303
1284
  return parser ? parser(ctx, options) : Promise.resolve({});
1304
1285
  }
1305
1286
  __name(parseBody, "parseBody");
1287
+ var cachedProject = null;
1288
+ var publicMethodsCache = /* @__PURE__ */ new Map();
1306
1289
  var ParamSourceType = /* @__PURE__ */ (function(ParamSourceType2) {
1307
1290
  ParamSourceType2["QUERY"] = "query";
1308
1291
  ParamSourceType2["BODY"] = "body";
@@ -1517,27 +1500,6 @@ function injectParamMetaData(app, target, options) {
1517
1500
  if (!v.sourceType) {
1518
1501
  v.sourceType = "custom";
1519
1502
  }
1520
- switch (v.sourceType) {
1521
- case "query":
1522
- v.extractorType = "query";
1523
- break;
1524
- case "body":
1525
- v.extractorType = "body";
1526
- break;
1527
- case "header":
1528
- v.extractorType = "header";
1529
- break;
1530
- case "path":
1531
- v.extractorType = "path";
1532
- break;
1533
- case "file":
1534
- v.extractorType = "file";
1535
- break;
1536
- case "custom":
1537
- v.extractorType = "custom";
1538
- break;
1539
- }
1540
- koatty_logger.DefaultLogger.Debug(`Set extractorType ${v.extractorType} for param ${v.name}`);
1541
1503
  const validEntry = validData.find((it) => v.index === it.index && it.name === v.name);
1542
1504
  if (validEntry) {
1543
1505
  v.validRule = validEntry.rule;
@@ -1611,15 +1573,6 @@ function injectParamMetaData(app, target, options) {
1611
1573
  }
1612
1574
  }
1613
1575
  });
1614
- const fastPathScenario = detectFastPathScenario(data);
1615
- if (fastPathScenario) {
1616
- const fastPathHandler = createFastPathHandler(fastPathScenario, data);
1617
- if (fastPathHandler) {
1618
- koatty_logger.DefaultLogger.Debug(`Created fast path handler for ${target.constructor.name}.${meta}, scenario: ${fastPathScenario}`);
1619
- data.fastPathHandler = fastPathHandler;
1620
- data.fastPathScenario = fastPathScenario;
1621
- }
1622
- }
1623
1576
  const hasAsyncParams = data.some(
1624
1577
  (p) => p.sourceType === "body" || p.sourceType === "file" || p.isDto
1625
1578
  // DTO validation might be async
@@ -1658,8 +1611,15 @@ var injectParam = /* @__PURE__ */ __name((fn, name, sourceType, paramName, defau
1658
1611
  };
1659
1612
  }, "injectParam");
1660
1613
  function getPublicMethods(classFilePath, className) {
1661
- const project = new tsMorph.Project();
1662
- const sourceFile = project.addSourceFileAtPath(classFilePath);
1614
+ const cacheKey = `${classFilePath}::${className}`;
1615
+ const cached = publicMethodsCache.get(cacheKey);
1616
+ if (cached) {
1617
+ return cached;
1618
+ }
1619
+ if (!cachedProject) {
1620
+ cachedProject = new tsMorph.Project();
1621
+ }
1622
+ const sourceFile = cachedProject.addSourceFileAtPath(classFilePath);
1663
1623
  const classDeclaration = sourceFile.getClass(className);
1664
1624
  const publicMethods = [];
1665
1625
  if (classDeclaration) {
@@ -1670,6 +1630,7 @@ function getPublicMethods(classFilePath, className) {
1670
1630
  }
1671
1631
  }
1672
1632
  }
1633
+ publicMethodsCache.set(cacheKey, publicMethods);
1673
1634
  return publicMethods;
1674
1635
  }
1675
1636
  __name(getPublicMethods, "getPublicMethods");
@@ -1677,104 +1638,6 @@ function getControllerPath(className) {
1677
1638
  return process.env.APP_PATH + "/controller/" + className + ".ts";
1678
1639
  }
1679
1640
  __name(getControllerPath, "getControllerPath");
1680
- function detectFastPathScenario(params) {
1681
- if (!params || params.length === 0) {
1682
- return null;
1683
- }
1684
- if (params.length === 1) {
1685
- const param = params[0];
1686
- const fnName = param.fn?.name || "";
1687
- if (fnName === "Get" && !param.validRule && !param.isDto) {
1688
- koatty_logger.DefaultLogger.Debug(`Detected fast path scenario A: single query param without validation`);
1689
- return "SINGLE_QUERY_NO_VALIDATION";
1690
- }
1691
- if ((fnName === "Post" || fnName === "RequestBody") && param.isDto) {
1692
- koatty_logger.DefaultLogger.Debug(`Detected fast path scenario B: single DTO from body`);
1693
- return "SINGLE_DTO_FROM_BODY";
1694
- }
1695
- }
1696
- if (params.length > 1) {
1697
- const allQueryNoValidation = params.every((param) => {
1698
- const fnName = param.fn?.name || "";
1699
- return fnName === "Get" && !param.validRule && !param.isDto;
1700
- });
1701
- if (allQueryNoValidation) {
1702
- koatty_logger.DefaultLogger.Debug(`Detected fast path scenario C: multiple query params without validation`);
1703
- return "MULTIPLE_QUERY_NO_VALIDATION";
1704
- }
1705
- }
1706
- return null;
1707
- }
1708
- __name(detectFastPathScenario, "detectFastPathScenario");
1709
- function createFastPathHandler(scenario, params) {
1710
- switch (scenario) {
1711
- case "SINGLE_QUERY_NO_VALIDATION": {
1712
- const param = params[0];
1713
- const paramName = param.name;
1714
- const paramType = param.type;
1715
- if (paramName) {
1716
- return (ctx) => {
1717
- const value = ctx.query?.[paramName];
1718
- const converted = koatty_validation.convertParamsType(value, paramType);
1719
- return [
1720
- converted
1721
- ];
1722
- };
1723
- } else {
1724
- return (ctx) => {
1725
- const query = ctx.query || {};
1726
- return [
1727
- query
1728
- ];
1729
- };
1730
- }
1731
- }
1732
- case "SINGLE_DTO_FROM_BODY": {
1733
- const param = params[0];
1734
- const clazz = param.clazz;
1735
- const dtoCheck = param.dtoCheck;
1736
- if (!clazz) {
1737
- koatty_logger.DefaultLogger.Debug(`Cannot create fast path: DTO class not found`);
1738
- return null;
1739
- }
1740
- return async (ctx) => {
1741
- const body = await bodyParser(ctx, param.options);
1742
- let validatedValue;
1743
- if (dtoCheck) {
1744
- validatedValue = await koatty_validation.ClassValidator.valid(clazz, body, true);
1745
- } else {
1746
- validatedValue = koatty_validation.plainToClass(clazz, body, true);
1747
- }
1748
- return [
1749
- validatedValue
1750
- ];
1751
- };
1752
- }
1753
- case "MULTIPLE_QUERY_NO_VALIDATION": {
1754
- const paramConfigs = params.map((p) => ({
1755
- name: p.name,
1756
- type: p.type
1757
- }));
1758
- return (ctx) => {
1759
- const query = ctx.query || {};
1760
- const results = [];
1761
- for (const config of paramConfigs) {
1762
- if (config.name) {
1763
- const value = query[config.name];
1764
- const converted = koatty_validation.convertParamsType(value, config.type);
1765
- results.push(converted);
1766
- } else {
1767
- results.push(query);
1768
- }
1769
- }
1770
- return results;
1771
- };
1772
- }
1773
- default:
1774
- return null;
1775
- }
1776
- }
1777
- __name(createFastPathHandler, "createFastPathHandler");
1778
1641
  function compileTypeConverter(type) {
1779
1642
  const normalizedType = type.toLowerCase();
1780
1643
  if (normalizedType === "string") {
@@ -1783,53 +1646,24 @@ function compileTypeConverter(type) {
1783
1646
  switch (normalizedType) {
1784
1647
  case "number":
1785
1648
  return (value) => {
1649
+ if (typeof value === "number") return value;
1786
1650
  if (value === null || value === void 0 || value === "") return value;
1787
- const num = Number(value);
1788
- return isNaN(num) ? value : num;
1651
+ return koatty_validation.convertParamsType(value, "number");
1789
1652
  };
1790
1653
  case "boolean":
1791
1654
  return (value) => {
1792
- if (value === null || value === void 0) return value;
1793
1655
  if (typeof value === "boolean") return value;
1794
- if (typeof value === "string") {
1795
- const lower = value.toLowerCase();
1796
- if (lower === "true" || lower === "1") return true;
1797
- if (lower === "false" || lower === "0") return false;
1798
- }
1799
- return Boolean(value);
1656
+ return koatty_validation.convertParamsType(value, "boolean");
1800
1657
  };
1801
1658
  case "array":
1802
1659
  return (value) => {
1803
- if (value === null || value === void 0) return value;
1804
1660
  if (Array.isArray(value)) return value;
1805
- if (typeof value === "string") {
1806
- try {
1807
- const parsed = JSON.parse(value);
1808
- return Array.isArray(parsed) ? parsed : [
1809
- value
1810
- ];
1811
- } catch {
1812
- return [
1813
- value
1814
- ];
1815
- }
1816
- }
1817
- return [
1818
- value
1819
- ];
1661
+ return koatty_validation.convertParamsType(value, "array");
1820
1662
  };
1821
1663
  case "object":
1822
1664
  return (value) => {
1823
- if (value === null || value === void 0) return value;
1824
- if (typeof value === "object") return value;
1825
- if (typeof value === "string") {
1826
- try {
1827
- return JSON.parse(value);
1828
- } catch {
1829
- return value;
1830
- }
1831
- }
1832
- return value;
1665
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
1666
+ return koatty_validation.convertParamsType(value, "object");
1833
1667
  };
1834
1668
  default:
1835
1669
  return (value) => koatty_validation.convertParamsType(value, type);
@@ -1857,7 +1691,22 @@ function generatePrecompiledExtractor(param) {
1857
1691
  } else {
1858
1692
  return (ctx) => ctx.params || {};
1859
1693
  }
1860
- case "body":
1694
+ case "body": {
1695
+ const bodyOpts = param.options;
1696
+ if (paramName !== void 0) {
1697
+ return async (ctx) => {
1698
+ const parsed = await bodyParser(ctx, bodyOpts);
1699
+ return (parsed ?? {})[paramName];
1700
+ };
1701
+ } else {
1702
+ return async (ctx) => {
1703
+ const parsed = await bodyParser(ctx, bodyOpts);
1704
+ if (!parsed) return {};
1705
+ const { [FILE_KEY]: _files, ...bodyOnly } = parsed;
1706
+ return bodyOnly;
1707
+ };
1708
+ }
1709
+ }
1861
1710
  case "file":
1862
1711
  return null;
1863
1712
  case "custom":
@@ -1920,14 +1769,19 @@ function extractValueFromSource(source, param) {
1920
1769
  switch (param.sourceType) {
1921
1770
  case ParamSourceType.QUERY:
1922
1771
  return paramName ? source.query?.[paramName] : source.query;
1923
- case ParamSourceType.BODY:
1924
- return paramName ? source.body?.[paramName] : source.body;
1772
+ case ParamSourceType.BODY: {
1773
+ if (paramName) return source.body?.[paramName];
1774
+ const { [FILE_KEY]: _files, ...bodyFields } = source.body || {};
1775
+ return bodyFields;
1776
+ }
1925
1777
  case ParamSourceType.HEADER:
1926
1778
  return paramName ? source.headers?.[paramName] : source.headers;
1927
1779
  case ParamSourceType.PATH:
1928
1780
  return paramName ? source.params?.[paramName] : source.params;
1929
- case ParamSourceType.FILE:
1930
- return paramName ? source.body?.file?.[paramName] : source.body?.file;
1781
+ case ParamSourceType.FILE: {
1782
+ const files = source.body?.[FILE_KEY] || {};
1783
+ return paramName ? files[paramName] : files;
1784
+ }
1931
1785
  case ParamSourceType.CUSTOM:
1932
1786
  return null;
1933
1787
  default:
@@ -1939,21 +1793,18 @@ async function extractParamSources(ctx, params) {
1939
1793
  const needsBody = params.some((param) => {
1940
1794
  return param.sourceType === ParamSourceType.BODY || param.sourceType === ParamSourceType.FILE;
1941
1795
  });
1942
- const bodyData = {};
1796
+ let body = {};
1943
1797
  if (needsBody) {
1944
1798
  try {
1945
1799
  const parsedBody = await bodyParser(ctx, params[0]?.options);
1946
- bodyData.body = parsedBody;
1947
- if (typeof parsedBody === "object" && "file" in parsedBody) {
1948
- bodyData.file = parsedBody.file;
1949
- }
1800
+ body = parsedBody || {};
1950
1801
  } catch (err) {
1951
1802
  koatty_logger.DefaultLogger.Error(`extractParamSources: Failed to parse body: ${err.message}`);
1952
1803
  }
1953
1804
  }
1954
1805
  return {
1955
1806
  query: ctx.query || {},
1956
- body: bodyData,
1807
+ body,
1957
1808
  params: ctx.params || {},
1958
1809
  headers: ctx.headers || {}
1959
1810
  };
@@ -1983,7 +1834,7 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
1983
1834
  }
1984
1835
  return validatedValue;
1985
1836
  } else {
1986
- const needsConversion = compiledTypeConverter !== null;
1837
+ const needsConversion = compiledTypeConverter != null;
1987
1838
  const needsValidation = !!(compiledValidator || opt.validRule);
1988
1839
  if (!needsConversion && !needsValidation) {
1989
1840
  return value;
@@ -1991,8 +1842,6 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
1991
1842
  let convertedValue = value;
1992
1843
  if (compiledTypeConverter) {
1993
1844
  convertedValue = compiledTypeConverter(value);
1994
- } else if (opt.type && opt.type !== "string") {
1995
- convertedValue = koatty_validation.convertParamsType(value, opt.type);
1996
1845
  }
1997
1846
  if (compiledValidator) {
1998
1847
  compiledValidator(convertedValue);
@@ -2297,17 +2146,27 @@ var StrategyHandlerFactory = class {
2297
2146
  return converted;
2298
2147
  };
2299
2148
  });
2149
+ const allAsyncHaveExtractor = asyncParams.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
2300
2150
  return async (ctx) => {
2301
- const bodyData = await extractParamSources(ctx, params);
2302
- const asyncResults = asyncParams.map((p) => {
2303
- const rawValue = extractValueFromSource(bodyData, p);
2304
- if (rawValue === null && p.fn) {
2305
- return p.fn(ctx, p.options);
2151
+ const bodyData = allAsyncHaveExtractor ? null : await extractParamSources(ctx, params);
2152
+ const asyncResults = asyncParams.map(async (p) => {
2153
+ if (p.precompiledExtractor) {
2154
+ const rawValue2 = await p.precompiledExtractor(ctx);
2155
+ const value = rawValue2 === void 0 && p.defaultValue !== void 0 ? p.defaultValue : rawValue2;
2156
+ const paramOptions2 = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2157
+ return validateParam(app, ctx, value, paramOptions2, p.compiledValidator, p.compiledTypeConverter);
2158
+ }
2159
+ if (p.fn && typeof p.fn === "function") {
2160
+ const rawValue2 = await Promise.resolve(p.fn(ctx, p.options));
2161
+ const value = rawValue2 === void 0 && p.defaultValue !== void 0 ? p.defaultValue : rawValue2;
2162
+ const paramOptions2 = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2163
+ return validateParam(app, ctx, value, paramOptions2, p.compiledValidator, p.compiledTypeConverter);
2306
2164
  }
2165
+ const rawValue = bodyData ? extractValueFromSource(bodyData, p) : void 0;
2307
2166
  if (rawValue === void 0 && p.defaultValue !== void 0) {
2308
2167
  return p.defaultValue;
2309
2168
  }
2310
- const paramOptions = p.precompiledOptions || createParamOptions(p, 0);
2169
+ const paramOptions = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2311
2170
  return validateParam(app, ctx, rawValue, paramOptions, p.compiledValidator, p.compiledTypeConverter);
2312
2171
  });
2313
2172
  const syncResults = syncHandlers.map((h) => h(ctx));
@@ -2360,17 +2219,27 @@ var StrategyHandlerFactory = class {
2360
2219
  * Fallback: Generic async path
2361
2220
  */
2362
2221
  static createGenericAsyncHandler(params, app) {
2222
+ const allHaveExtractorOrFn = params.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
2363
2223
  return async (ctx) => {
2364
- const sources = await extractParamSources(ctx, params);
2365
- const paramPromises = params.map((v, k) => {
2366
- let rawValue = extractValueFromSource(sources, v);
2367
- if (rawValue === null && v.fn) {
2368
- rawValue = v.fn(ctx, v.options);
2224
+ const sources = allHaveExtractorOrFn ? null : await extractParamSources(ctx, params);
2225
+ const paramPromises = params.map(async (v, k) => {
2226
+ if (v.precompiledExtractor) {
2227
+ const rawValue2 = await v.precompiledExtractor(ctx);
2228
+ const value = rawValue2 === void 0 && v.defaultValue !== void 0 ? v.defaultValue : rawValue2;
2229
+ const paramOptions2 = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2230
+ return validateParam(app, ctx, value, paramOptions2, v.compiledValidator, v.compiledTypeConverter);
2369
2231
  }
2232
+ if (v.fn && typeof v.fn === "function") {
2233
+ const rawValue2 = await Promise.resolve(v.fn(ctx, v.options));
2234
+ const value = rawValue2 === void 0 && v.defaultValue !== void 0 ? v.defaultValue : rawValue2;
2235
+ const paramOptions2 = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2236
+ return validateParam(app, ctx, value, paramOptions2, v.compiledValidator, v.compiledTypeConverter);
2237
+ }
2238
+ let rawValue = sources ? extractValueFromSource(sources, v) : void 0;
2370
2239
  if (rawValue === void 0 && v.defaultValue !== void 0) {
2371
- rawValue = v.defaultValue;
2240
+ return v.defaultValue;
2372
2241
  }
2373
- const paramOptions = v.precompiledOptions || createParamOptions(v, k);
2242
+ const paramOptions = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2374
2243
  return validateParam(app, ctx, rawValue, paramOptions, v.compiledValidator, v.compiledTypeConverter);
2375
2244
  });
2376
2245
  return Promise.all(paramPromises);
@@ -2493,6 +2362,8 @@ function validateProtocolConfig(protocol, ext = {}, env = process.env.NODE_ENV |
2493
2362
  break;
2494
2363
  case "http":
2495
2364
  case "https":
2365
+ case "http2":
2366
+ case "http3":
2496
2367
  break;
2497
2368
  default:
2498
2369
  result.valid = false;
@@ -2524,7 +2395,7 @@ var GraphQLRouter = class {
2524
2395
  validation.warnings.forEach((warning) => koatty_logger.DefaultLogger.Warn(`[GraphQLRouter] ${warning}`));
2525
2396
  }
2526
2397
  let schemaFilePath = extConfig.schemaFile;
2527
- if (schemaFilePath && !path__default.default.isAbsolute(schemaFilePath)) {
2398
+ if (schemaFilePath && !path__default.default.isAbsolute(schemaFilePath) && app.rootPath) {
2528
2399
  schemaFilePath = path__default.default.resolve(app.rootPath, schemaFilePath);
2529
2400
  }
2530
2401
  this.options = {
@@ -2581,7 +2452,19 @@ var GraphQLRouter = class {
2581
2452
  rootValue: routeHandler,
2582
2453
  validationRules: validationRules.length > 0 ? validationRules : void 0,
2583
2454
  formatError: this.options.ext?.debug ? void 0 : (error) => {
2584
- return new Error(error.message);
2455
+ const formatted = {
2456
+ message: error.message
2457
+ };
2458
+ if (error.extensions) {
2459
+ formatted.extensions = error.extensions;
2460
+ }
2461
+ if (error.locations) {
2462
+ formatted.locations = error.locations;
2463
+ }
2464
+ if (error.path) {
2465
+ formatted.path = error.path;
2466
+ }
2467
+ return formatted;
2585
2468
  },
2586
2469
  context: /* @__PURE__ */ __name((req) => {
2587
2470
  return req.koattyContext;
@@ -2639,6 +2522,13 @@ var GraphQLRouter = class {
2639
2522
  this.routerMap.set(name, impl);
2640
2523
  }
2641
2524
  /**
2525
+ * Escape string for safe JavaScript embedding
2526
+ * @private
2527
+ */
2528
+ escapeJsString(str) {
2529
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/</g, "\\x3c").replace(/>/g, "\\x3e").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
2530
+ }
2531
+ /**
2642
2532
  * Render GraphiQL interface
2643
2533
  *
2644
2534
  * @private
@@ -2646,6 +2536,7 @@ var GraphQLRouter = class {
2646
2536
  * @returns {string} HTML content for GraphiQL
2647
2537
  */
2648
2538
  renderGraphiQL(endpoint) {
2539
+ const safeEndpoint = this.escapeJsString(endpoint);
2649
2540
  return `<!DOCTYPE html>
2650
2541
  <html>
2651
2542
  <head>
@@ -2673,7 +2564,7 @@ var GraphQLRouter = class {
2673
2564
  ></script>
2674
2565
  <script>
2675
2566
  const fetcher = GraphiQL.createFetcher({
2676
- url: '${endpoint}',
2567
+ url: '${safeEndpoint}',
2677
2568
  });
2678
2569
  const root = ReactDOM.createRoot(document.getElementById('graphiql'));
2679
2570
  root.render(
@@ -2755,185 +2646,6 @@ var GraphQLRouter = class {
2755
2646
  koatty_logger.DefaultLogger.Debug("GraphQL router cleanup completed");
2756
2647
  }
2757
2648
  };
2758
- var GrpcConnectionPool = class GrpcConnectionPool2 {
2759
- static {
2760
- __name(this, "GrpcConnectionPool");
2761
- }
2762
- pool;
2763
- maxSize;
2764
- constructor(maxSize = 10) {
2765
- this.pool = /* @__PURE__ */ new Map();
2766
- this.maxSize = maxSize;
2767
- }
2768
- /**
2769
- * Get connection from pool or create new one
2770
- */
2771
- get(serviceName, options) {
2772
- const connections = this.pool.get(serviceName);
2773
- if (connections && connections.length > 0) {
2774
- const conn = connections.pop();
2775
- koatty_logger.DefaultLogger.Debug(`Reused connection from pool for service: ${serviceName}`);
2776
- return conn;
2777
- }
2778
- koatty_logger.DefaultLogger.Debug(`Creating new connection for service: ${serviceName}`);
2779
- return this.create(serviceName, options);
2780
- }
2781
- /**
2782
- * Release connection back to pool
2783
- */
2784
- release(serviceName, connection) {
2785
- if (!connection) return;
2786
- if (!this.pool.has(serviceName)) {
2787
- this.pool.set(serviceName, []);
2788
- }
2789
- const connections = this.pool.get(serviceName);
2790
- if (connections.length < this.maxSize) {
2791
- connections.push(connection);
2792
- koatty_logger.DefaultLogger.Debug(`Connection released back to pool for service: ${serviceName}, pool size: ${connections.length}`);
2793
- } else {
2794
- if (connection.close && typeof connection.close === "function") {
2795
- connection.close();
2796
- }
2797
- koatty_logger.DefaultLogger.Debug(`Pool full for service: ${serviceName}, connection closed`);
2798
- }
2799
- }
2800
- /**
2801
- * Create new connection
2802
- * @param serviceName - Name of the gRPC service
2803
- * @param options - gRPC client options
2804
- * @returns Connection object (placeholder, actual implementation depends on gRPC client library)
2805
- */
2806
- create(serviceName, options) {
2807
- koatty_logger.DefaultLogger.Debug(`Creating connection stub for service: ${serviceName}`);
2808
- return {
2809
- serviceName,
2810
- createdAt: Date.now(),
2811
- options,
2812
- // Placeholder methods
2813
- close: /* @__PURE__ */ __name(() => {
2814
- koatty_logger.DefaultLogger.Debug(`Closing connection for service: ${serviceName}`);
2815
- }, "close")
2816
- };
2817
- }
2818
- /**
2819
- * Cleanup all connections in the pool
2820
- */
2821
- clear() {
2822
- let totalConnections = 0;
2823
- for (const [_serviceName, connections] of this.pool.entries()) {
2824
- for (const connection of connections) {
2825
- if (connection && connection.close && typeof connection.close === "function") {
2826
- connection.close();
2827
- }
2828
- totalConnections++;
2829
- }
2830
- }
2831
- this.pool.clear();
2832
- koatty_logger.DefaultLogger.Info(`gRPC connection pool cleared, closed ${totalConnections} connections`);
2833
- }
2834
- /**
2835
- * Get pool statistics
2836
- */
2837
- getStats() {
2838
- const stats = {};
2839
- for (const [serviceName, connections] of this.pool.entries()) {
2840
- stats[serviceName] = connections.length;
2841
- }
2842
- return stats;
2843
- }
2844
- };
2845
- var GrpcBatchProcessor = class GrpcBatchProcessor2 {
2846
- static {
2847
- __name(this, "GrpcBatchProcessor");
2848
- }
2849
- batchSize;
2850
- batchQueue;
2851
- batchTimers;
2852
- constructor(batchSize = 10) {
2853
- this.batchSize = batchSize;
2854
- this.batchQueue = /* @__PURE__ */ new Map();
2855
- this.batchTimers = /* @__PURE__ */ new Map();
2856
- }
2857
- /**
2858
- * Add request to batch
2859
- */
2860
- addRequest(serviceName, request) {
2861
- return new Promise((resolve, reject) => {
2862
- if (!this.batchQueue.has(serviceName)) {
2863
- this.batchQueue.set(serviceName, []);
2864
- }
2865
- const queue = this.batchQueue.get(serviceName);
2866
- queue.push({
2867
- request,
2868
- resolve,
2869
- reject
2870
- });
2871
- if (queue.length >= this.batchSize) {
2872
- this.processBatch(serviceName);
2873
- } else if (!this.batchTimers.has(serviceName)) {
2874
- this.batchTimers.set(serviceName, setTimeout(() => {
2875
- this.processBatch(serviceName);
2876
- }, 100));
2877
- }
2878
- });
2879
- }
2880
- /**
2881
- * Process batch of requests
2882
- */
2883
- processBatch(serviceName) {
2884
- const queue = this.batchQueue.get(serviceName);
2885
- if (!queue || queue.length === 0) return;
2886
- const timer = this.batchTimers.get(serviceName);
2887
- if (timer) {
2888
- clearTimeout(timer);
2889
- this.batchTimers.delete(serviceName);
2890
- }
2891
- koatty_logger.DefaultLogger.Debug(`Processing batch for service ${serviceName}, ${queue.length} requests`);
2892
- queue.forEach((item, index) => {
2893
- try {
2894
- item.resolve({
2895
- success: true,
2896
- data: item.request,
2897
- batchIndex: index,
2898
- batchSize: queue.length
2899
- });
2900
- } catch (error) {
2901
- item.reject(error);
2902
- }
2903
- });
2904
- this.batchQueue.delete(serviceName);
2905
- koatty_logger.DefaultLogger.Debug(`Batch processing completed for service ${serviceName}`);
2906
- }
2907
- /**
2908
- * Flush all pending batches and cleanup
2909
- */
2910
- flush() {
2911
- let totalProcessed = 0;
2912
- for (const serviceName of this.batchQueue.keys()) {
2913
- const queueSize = this.batchQueue.get(serviceName)?.length || 0;
2914
- this.processBatch(serviceName);
2915
- totalProcessed += queueSize;
2916
- }
2917
- for (const timer of this.batchTimers.values()) {
2918
- clearTimeout(timer);
2919
- }
2920
- this.batchTimers.clear();
2921
- koatty_logger.DefaultLogger.Info(`gRPC batch processor flushed, processed ${totalProcessed} pending requests`);
2922
- }
2923
- /**
2924
- * Get batch queue statistics
2925
- */
2926
- getStats() {
2927
- const stats = [];
2928
- for (const [serviceName, queue] of this.batchQueue.entries()) {
2929
- stats.push({
2930
- serviceName,
2931
- queueSize: queue.length
2932
- });
2933
- }
2934
- return stats;
2935
- }
2936
- };
2937
2649
  var StreamManager = class StreamManager2 {
2938
2650
  static {
2939
2651
  __name(this, "StreamManager");
@@ -2946,7 +2658,6 @@ var StreamManager = class StreamManager2 {
2946
2658
  maxConcurrentStreams: config.maxConcurrentStreams || 100,
2947
2659
  streamTimeout: config.streamTimeout || 3e5,
2948
2660
  backpressureThreshold: config.backpressureThreshold || 1e3,
2949
- bufferSize: config.bufferSize || 64 * 1024,
2950
2661
  ...config
2951
2662
  };
2952
2663
  }
@@ -2967,6 +2678,18 @@ var StreamManager = class StreamManager2 {
2967
2678
  return state;
2968
2679
  }
2969
2680
  /**
2681
+ * 移除流
2682
+ */
2683
+ removeStream(id) {
2684
+ this.streams.delete(id);
2685
+ }
2686
+ /**
2687
+ * 获取流状态
2688
+ */
2689
+ getStreamState(id) {
2690
+ return this.streams.get(id);
2691
+ }
2692
+ /**
2970
2693
  * 更新流状态
2971
2694
  */
2972
2695
  updateStream(id, updates) {
@@ -2976,12 +2699,6 @@ var StreamManager = class StreamManager2 {
2976
2699
  }
2977
2700
  }
2978
2701
  /**
2979
- * 移除流
2980
- */
2981
- removeStream(id) {
2982
- this.streams.delete(id);
2983
- }
2984
- /**
2985
2702
  * 检查是否达到背压阈值
2986
2703
  */
2987
2704
  isBackpressureTriggered(id) {
@@ -3031,8 +2748,6 @@ var GrpcRouter = class {
3031
2748
  protocol;
3032
2749
  options;
3033
2750
  router;
3034
- connectionPool;
3035
- batchProcessor;
3036
2751
  streamManager;
3037
2752
  constructor(app, options = {
3038
2753
  protocol: "grpc",
@@ -3047,20 +2762,16 @@ var GrpcRouter = class {
3047
2762
  validation.warnings.forEach((warning) => koatty_logger.DefaultLogger.Warn(`[GrpcRouter] ${warning}`));
3048
2763
  }
3049
2764
  let protoFilePath = extConfig.protoFile;
3050
- if (protoFilePath && !path__default.default.isAbsolute(protoFilePath)) {
2765
+ if (protoFilePath && !path__default.default.isAbsolute(protoFilePath) && app.rootPath) {
3051
2766
  protoFilePath = path__default.default.resolve(app.rootPath, protoFilePath);
3052
2767
  }
3053
2768
  this.options = {
3054
2769
  ...options,
3055
2770
  protoFile: protoFilePath,
3056
- poolSize: extConfig.poolSize || 10,
3057
- batchSize: extConfig.batchSize || 10,
3058
2771
  streamConfig: extConfig.streamConfig || {}
3059
2772
  };
3060
2773
  this.protocol = options.protocol || "grpc";
3061
2774
  this.router = /* @__PURE__ */ new Map();
3062
- this.connectionPool = new GrpcConnectionPool(this.options.poolSize);
3063
- this.batchProcessor = new GrpcBatchProcessor(this.options.batchSize);
3064
2775
  this.streamManager = new StreamManager(this.options.streamConfig);
3065
2776
  }
3066
2777
  /**
@@ -3133,7 +2844,7 @@ var GrpcRouter = class {
3133
2844
  */
3134
2845
  async handleServerStreaming(call, app, ctlItem) {
3135
2846
  const streamId = `server_${Date.now()}_${Math.random()}`;
3136
- const streamState = this.streamManager.registerStream(streamId, "server_streaming");
2847
+ this.streamManager.registerStream(streamId, "server_streaming");
3137
2848
  try {
3138
2849
  koatty_logger.DefaultLogger.Debug(`[GRPC_ROUTER] Handling server streaming call for ${ctlItem.name}.${ctlItem.method}`);
3139
2850
  const timeout = setTimeout(() => {
@@ -3158,9 +2869,10 @@ var GrpcRouter = class {
3158
2869
  return false;
3159
2870
  }
3160
2871
  call.write(data);
3161
- this.streamManager.updateStream(streamId, {
3162
- messageCount: streamState.messageCount + 1
3163
- });
2872
+ const currentState = this.streamManager.getStreamState(streamId);
2873
+ if (currentState) {
2874
+ currentState.messageCount++;
2875
+ }
3164
2876
  return true;
3165
2877
  };
3166
2878
  ctx.endStream = () => {
@@ -3183,7 +2895,7 @@ var GrpcRouter = class {
3183
2895
  */
3184
2896
  handleClientStreaming(call, callback, app, ctlItem) {
3185
2897
  const streamId = `client_${Date.now()}_${Math.random()}`;
3186
- const streamState = this.streamManager.registerStream(streamId, "client_streaming");
2898
+ this.streamManager.registerStream(streamId, "client_streaming");
3187
2899
  const messages = [];
3188
2900
  try {
3189
2901
  koatty_logger.DefaultLogger.Debug(`[GRPC_ROUTER] Handling client streaming call for ${ctlItem.name}.${ctlItem.method}`);
@@ -3194,10 +2906,11 @@ var GrpcRouter = class {
3194
2906
  }, this.options.streamConfig?.streamTimeout || 3e5);
3195
2907
  call.on("data", (data) => {
3196
2908
  messages.push(data);
3197
- this.streamManager.updateStream(streamId, {
3198
- messageCount: streamState.messageCount + 1,
3199
- bufferSize: streamState.bufferSize + JSON.stringify(data).length
3200
- });
2909
+ const currentState = this.streamManager.getStreamState(streamId);
2910
+ if (currentState) {
2911
+ currentState.messageCount++;
2912
+ currentState.bufferSize += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(JSON.stringify(data));
2913
+ }
3201
2914
  if (this.streamManager.isBackpressureTriggered(streamId)) {
3202
2915
  koatty_logger.DefaultLogger.Warn(`[GRPC_ROUTER] Backpressure triggered for client stream ${streamId}`);
3203
2916
  call.pause();
@@ -3245,7 +2958,7 @@ var GrpcRouter = class {
3245
2958
  */
3246
2959
  handleBidirectionalStreaming(call, app, ctlItem) {
3247
2960
  const streamId = `bidi_${Date.now()}_${Math.random()}`;
3248
- const streamState = this.streamManager.registerStream(streamId, "bidirectional_streaming");
2961
+ this.streamManager.registerStream(streamId, "bidirectional_streaming");
3249
2962
  try {
3250
2963
  koatty_logger.DefaultLogger.Debug(`[GRPC_ROUTER] Handling bidirectional streaming call for ${ctlItem.name}.${ctlItem.method}`);
3251
2964
  const timeout = setTimeout(() => {
@@ -3254,10 +2967,11 @@ var GrpcRouter = class {
3254
2967
  this.streamManager.removeStream(streamId);
3255
2968
  }, this.options.streamConfig?.streamTimeout || 3e5);
3256
2969
  call.on("data", async (data) => {
3257
- this.streamManager.updateStream(streamId, {
3258
- messageCount: streamState.messageCount + 1,
3259
- bufferSize: streamState.bufferSize + JSON.stringify(data).length
3260
- });
2970
+ const currentState = this.streamManager.getStreamState(streamId);
2971
+ if (currentState) {
2972
+ currentState.messageCount++;
2973
+ currentState.bufferSize += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(JSON.stringify(data));
2974
+ }
3261
2975
  if (this.streamManager.isBackpressureTriggered(streamId)) {
3262
2976
  koatty_logger.DefaultLogger.Warn(`[GRPC_ROUTER] Backpressure triggered for bidirectional stream ${streamId}`);
3263
2977
  call.pause();
@@ -3394,8 +3108,12 @@ var GrpcRouter = class {
3394
3108
  }
3395
3109
  koatty_logger.DefaultLogger.Debug(`[GRPC_ROUTER] \u2705 Register request mapping: ["${path3}" => ${ctlItem.name}.${ctlItem.method}]`);
3396
3110
  impl[handler.name] = (call, callback) => {
3397
- koatty_logger.DefaultLogger.Warn(`[GRPC_ROUTER] \u26A0\uFE0F Placeholder handler called for: ${handler.name} - this should not happen!`);
3398
- callback(new Error("This handler should not be called - routing should go through middleware"));
3111
+ const methodPath = `${si.name}/${handler.name}`;
3112
+ koatty_logger.DefaultLogger.Warn(`[GRPC_ROUTER] Placeholder handler invoked for ${methodPath}. No controller matched this gRPC method.`);
3113
+ callback({
3114
+ code: 12,
3115
+ message: `Method ${methodPath} not implemented. Ensure a controller with @GrpcMethod('${handler.name}') is registered.`
3116
+ });
3399
3117
  };
3400
3118
  }
3401
3119
  if (Object.keys(impl).length > 0) {
@@ -3480,8 +3198,6 @@ var GrpcRouter = class {
3480
3198
  cleanup() {
3481
3199
  koatty_logger.DefaultLogger.Info("Starting gRPC router cleanup...");
3482
3200
  this.streamManager.closeAllStreams();
3483
- this.batchProcessor.flush();
3484
- this.connectionPool.clear();
3485
3201
  koatty_logger.DefaultLogger.Info("gRPC router cleanup completed");
3486
3202
  }
3487
3203
  };
@@ -3516,7 +3232,7 @@ var HttpRouter = class {
3516
3232
  * @returns
3517
3233
  */
3518
3234
  SetRouter(name, impl) {
3519
- if (Helper5__namespace.isEmpty(impl.path)) return;
3235
+ if (!impl || Helper5__namespace.isEmpty(impl.path)) return;
3520
3236
  const method = (impl.method || "").toLowerCase();
3521
3237
  const routeHandler = impl.implementation;
3522
3238
  if ([
@@ -3731,7 +3447,7 @@ var WebsocketRouter = class {
3731
3447
  * @returns
3732
3448
  */
3733
3449
  SetRouter(name, impl) {
3734
- if (Helper5.Helper.isEmpty(impl.path)) return;
3450
+ if (!impl || Helper5.Helper.isEmpty(impl.path)) return;
3735
3451
  const routeHandler = impl.implementation;
3736
3452
  this.router.get(impl.path, routeHandler);
3737
3453
  this.routerMap.set(name, impl);
@@ -3970,6 +3686,10 @@ var RouterFactory = class _RouterFactory {
3970
3686
  * Create router instance
3971
3687
  */
3972
3688
  create(protocol, app, options) {
3689
+ if (this.hasShutdown) {
3690
+ koatty_logger.DefaultLogger.Debug("RouterFactory: Resetting shutdown state for new router creation");
3691
+ this.hasShutdown = false;
3692
+ }
3973
3693
  const normalizedProtocol = protocol.toLowerCase();
3974
3694
  const RouterClass = this.routerRegistry.get(normalizedProtocol);
3975
3695
  if (!RouterClass) {
@@ -4190,17 +3910,28 @@ function Get(name, defaultValue) {
4190
3910
  }
4191
3911
  __name(Get, "Get");
4192
3912
  function Post(name, defaultValue) {
4193
- return injectParam(async (ctx, opt) => {
4194
- const data = await bodyParser(ctx, opt);
4195
- const params = data.body ?? data;
4196
- return name ? params[name] : params;
4197
- }, "Post", ParamSourceType.BODY, name, defaultValue);
3913
+ return injectParam(
3914
+ // NOTE: This fn is a fallback path only. At runtime the strategy extractor uses
3915
+ // param.precompiledExtractor (generated by generatePrecompiledExtractor for BODY type)
3916
+ // which bypasses this fn entirely. This fn is invoked only when precompiledExtractor
3917
+ // is unavailable (e.g. startup compilation failure or CUSTOM source type fallback).
3918
+ async (ctx, opt) => {
3919
+ const data = await bodyParser(ctx, opt);
3920
+ if (name) return data[name];
3921
+ const { [FILE_KEY]: _files, ...bodyOnly } = data || {};
3922
+ return bodyOnly;
3923
+ },
3924
+ "Post",
3925
+ ParamSourceType.BODY,
3926
+ name,
3927
+ defaultValue
3928
+ );
4198
3929
  }
4199
3930
  __name(Post, "Post");
4200
3931
  function File(name, defaultValue) {
4201
3932
  return injectParam(async (ctx, opt) => {
4202
3933
  const body = await bodyParser(ctx, opt);
4203
- const params = body.file ?? {};
3934
+ const params = body[FILE_KEY] ?? {};
4204
3935
  return name ? params[name] : params;
4205
3936
  }, "File", ParamSourceType.FILE, name, defaultValue);
4206
3937
  }