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.mjs CHANGED
@@ -10,7 +10,7 @@ import { buildSchema } from 'koatty_graphql';
10
10
  import 'reflect-metadata';
11
11
  import { OnEvent, AppEvent, Component, CONTROLLER_ROUTER, KoattyApplication } from 'koatty_core';
12
12
  import { Project } from 'ts-morph';
13
- import { ClassValidator, plainToClass, convertParamsType, PARAM_RULE_KEY, PARAM_CHECK_KEY, PARAM_TYPE_KEY, FunctionValidator, paramterTypes } from 'koatty_validation';
13
+ import { ClassValidator, plainToClass, PARAM_RULE_KEY, PARAM_CHECK_KEY, PARAM_TYPE_KEY, FunctionValidator, convertParamsType, paramterTypes } from 'koatty_validation';
14
14
  import compose from 'koa-compose';
15
15
  import { LRUCache } from 'lru-cache';
16
16
  import getRawBody from 'raw-body';
@@ -24,7 +24,7 @@ import { LoadProto, ListServices } from 'koatty_proto';
24
24
 
25
25
  /*!
26
26
  * @Author: richen
27
- * @Date: 2026-03-07 15:34:44
27
+ * @Date: 2026-04-24 08:20:32
28
28
  * @License: BSD (3-Clause)
29
29
  * @Copyright (c) - <richenlin(at)gmail.com>
30
30
  * @HomePage: https://koatty.org/
@@ -161,9 +161,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
161
161
  headerCache = new LRUCache({
162
162
  max: 100
163
163
  });
164
- // 缓存清理定时器
165
- cacheCleanupTimer;
166
- CACHE_CLEANUP_INTERVAL = 5 * 60 * 1e3;
167
164
  /**
168
165
  * Private constructor to enforce singleton pattern
169
166
  */
@@ -174,7 +171,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
174
171
  }
175
172
  this._instanceId = Math.random().toString(36).substr(2, 9);
176
173
  DefaultLogger.Debug(`RouterMiddlewareManager instance created with ID: ${this._instanceId}`);
177
- this.startCacheCleanup();
178
174
  }
179
175
  /**
180
176
  * Get singleton instance
@@ -208,22 +204,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
208
204
  DefaultLogger.Debug("RouterMiddlewareManager singleton instance reset");
209
205
  }
210
206
  /**
211
- * Start cache cleanup timer
212
- */
213
- startCacheCleanup() {
214
- this.cacheCleanupTimer = setInterval(() => {
215
- this.performCacheCleanup();
216
- }, this.CACHE_CLEANUP_INTERVAL);
217
- }
218
- /**
219
- * Perform periodic cache cleanup
220
- */
221
- performCacheCleanup() {
222
- const beforeSize = this.getCacheSize();
223
- const afterSize = this.getCacheSize();
224
- DefaultLogger.Debug(`Cache cleanup completed. Size: ${beforeSize} -> ${afterSize}`);
225
- }
226
- /**
227
207
  * Get total cache size
228
208
  */
229
209
  getCacheSize() {
@@ -233,10 +213,6 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
233
213
  * Destroy manager and cleanup resources
234
214
  */
235
215
  destroy() {
236
- if (this.cacheCleanupTimer) {
237
- clearInterval(this.cacheCleanupTimer);
238
- this.cacheCleanupTimer = void 0;
239
- }
240
216
  this.clearCaches();
241
217
  this.middlewares.clear();
242
218
  DefaultLogger.Debug(`RouterMiddlewareManager instance ${this._instanceId} destroyed`);
@@ -710,9 +686,9 @@ var RouterMiddlewareManager = class _RouterMiddlewareManager {
710
686
  /**
711
687
  * Create middleware group
712
688
  */
713
- createGroup(groupName, middlewareNames) {
689
+ async createGroup(groupName, middlewareNames) {
714
690
  const groupMiddleware = this.compose(middlewareNames);
715
- this.register({
691
+ await this.register({
716
692
  name: groupName,
717
693
  middleware: groupMiddleware,
718
694
  metadata: {
@@ -783,6 +759,9 @@ function RegisterMiddleware(app, config) {
783
759
  };
784
760
  }
785
761
  __name(RegisterMiddleware, "RegisterMiddleware");
762
+
763
+ // src/payload/interface.ts
764
+ var FILE_KEY = /* @__PURE__ */ Symbol.for("koatty.files");
786
765
  function parseText(ctx, opts) {
787
766
  return getRawBody(inflate(ctx.req), opts).catch((err) => {
788
767
  DefaultLogger.Error(err);
@@ -794,8 +773,9 @@ async function parseJson(ctx, opts) {
794
773
  const str = await parseText(ctx, opts);
795
774
  if (!str) return {};
796
775
  try {
797
- return {
798
- body: JSON.parse(str)
776
+ const parsed = JSON.parse(str);
777
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
778
+ value: parsed
799
779
  };
800
780
  } catch (error) {
801
781
  DefaultLogger.Error(error);
@@ -804,18 +784,13 @@ async function parseJson(ctx, opts) {
804
784
  }
805
785
  __name(parseJson, "parseJson");
806
786
  async function parseForm(ctx, opts) {
807
- if (!ctx.request.headers["content-length"] || !ctx.request.headers["content-type"]?.includes("application/x-www-form-urlencoded")) {
808
- return {};
809
- }
810
787
  const str = await parseText(ctx, opts);
811
788
  if (!str || str.trim().length === 0) {
812
789
  return {};
813
790
  }
814
791
  try {
815
792
  const result = parse(str);
816
- return {
817
- body: result
818
- };
793
+ return result;
819
794
  } catch (error) {
820
795
  DefaultLogger.Error("[FormParseError]", error);
821
796
  return {};
@@ -847,12 +822,6 @@ __name(deleteFiles, "deleteFiles");
847
822
 
848
823
  // src/payload/parser/multipart.ts
849
824
  function parseMultipart(ctx, opts) {
850
- if (!ctx.request.headers["content-type"]?.includes("multipart/form-data")) {
851
- return Promise.resolve({
852
- body: {},
853
- file: {}
854
- });
855
- }
856
825
  const form = new IncomingForm({
857
826
  encoding: opts.encoding,
858
827
  multiples: opts.multiples,
@@ -875,15 +844,12 @@ function parseMultipart(ctx, opts) {
875
844
  if (err) {
876
845
  cleanup();
877
846
  DefaultLogger.Error("[MultipartParseError]", err);
878
- return resolve({
879
- body: {},
880
- file: {}
881
- });
847
+ return resolve({});
882
848
  }
883
849
  uploadFiles = files;
884
850
  resolve({
885
- body: fields,
886
- file: files
851
+ ...fields,
852
+ [FILE_KEY]: files
887
853
  });
888
854
  });
889
855
  });
@@ -897,8 +863,9 @@ async function parseXml(ctx, opts) {
897
863
  const str = await parseText(ctx, opts);
898
864
  if (!str) return {};
899
865
  try {
900
- return {
901
- body: xmlParser.parse(str)
866
+ const parsed = xmlParser.parse(str);
867
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
868
+ value: parsed
902
869
  };
903
870
  } catch (error) {
904
871
  DefaultLogger.Error(error);
@@ -939,11 +906,14 @@ async function parseWebSocket(ctx, opts) {
939
906
  const str = await parseText(ctx, opts);
940
907
  if (!str) return {};
941
908
  try {
942
- return {
943
- body: JSON.parse(str)
909
+ const parsed = JSON.parse(str);
910
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
911
+ value: parsed
944
912
  };
945
913
  } catch {
946
- return str;
914
+ return {
915
+ value: str
916
+ };
947
917
  }
948
918
  } catch (error) {
949
919
  DefaultLogger.Error("[WebSocketParseError]", error);
@@ -1163,6 +1133,7 @@ var PayloadCacheManager = class _PayloadCacheManager {
1163
1133
  var cacheManager = PayloadCacheManager.getInstance();
1164
1134
 
1165
1135
  // src/payload/payload.ts
1136
+ var bodyCache = /* @__PURE__ */ new WeakMap();
1166
1137
  var supportedMethods = /* @__PURE__ */ new Set([
1167
1138
  "POST",
1168
1139
  "PUT",
@@ -1220,7 +1191,7 @@ function payload(options) {
1220
1191
  if (!Object.prototype.hasOwnProperty.call(ctx, "requestFile")) {
1221
1192
  Helper.define(ctx, "requestFile", async (name) => {
1222
1193
  const body = await bodyParser(ctx, opts);
1223
- const files = body.file ?? {};
1194
+ const files = body[FILE_KEY] ?? {};
1224
1195
  return name ? files[name] : files;
1225
1196
  });
1226
1197
  }
@@ -1240,22 +1211,32 @@ function queryParser(ctx, _options) {
1240
1211
  return Object.assign({}, query, params);
1241
1212
  }
1242
1213
  __name(queryParser, "queryParser");
1243
- async function bodyParser(ctx, options) {
1214
+ function bodyParser(ctx, options) {
1215
+ const cached = bodyCache.get(ctx);
1216
+ if (cached !== void 0) return cached;
1217
+ return parseBodyAndCache(ctx, options);
1218
+ }
1219
+ __name(bodyParser, "bodyParser");
1220
+ async function parseBodyAndCache(ctx, options) {
1244
1221
  try {
1245
- let body = ctx.getMetaData("_body")[0];
1246
- if (!Helper.isEmpty(body)) {
1247
- return body;
1248
- }
1222
+ const cached = bodyCache.get(ctx);
1223
+ if (cached !== void 0) return cached;
1249
1224
  const opts = cacheManager.getMergedOptions(options);
1250
- body = await parseBody(ctx, opts);
1251
- ctx.setMetaData("_body", body);
1225
+ const body = await parseBody(ctx, opts);
1226
+ bodyCache.set(ctx, body);
1227
+ try {
1228
+ ctx.setMetaData("_body", body);
1229
+ } catch {
1230
+ }
1252
1231
  return body;
1253
1232
  } catch (err) {
1254
1233
  DefaultLogger.Error(err);
1255
- return {};
1234
+ const empty = {};
1235
+ bodyCache.set(ctx, empty);
1236
+ return empty;
1256
1237
  }
1257
1238
  }
1258
- __name(bodyParser, "bodyParser");
1239
+ __name(parseBodyAndCache, "parseBodyAndCache");
1259
1240
  function parseBody(ctx, options) {
1260
1241
  if (!isSupportedMethod(ctx.method)) {
1261
1242
  return Promise.resolve({});
@@ -1273,6 +1254,8 @@ function parseBody(ctx, options) {
1273
1254
  return parser ? parser(ctx, options) : Promise.resolve({});
1274
1255
  }
1275
1256
  __name(parseBody, "parseBody");
1257
+ var cachedProject = null;
1258
+ var publicMethodsCache = /* @__PURE__ */ new Map();
1276
1259
  var ParamSourceType = /* @__PURE__ */ (function(ParamSourceType2) {
1277
1260
  ParamSourceType2["QUERY"] = "query";
1278
1261
  ParamSourceType2["BODY"] = "body";
@@ -1487,27 +1470,6 @@ function injectParamMetaData(app, target, options) {
1487
1470
  if (!v.sourceType) {
1488
1471
  v.sourceType = "custom";
1489
1472
  }
1490
- switch (v.sourceType) {
1491
- case "query":
1492
- v.extractorType = "query";
1493
- break;
1494
- case "body":
1495
- v.extractorType = "body";
1496
- break;
1497
- case "header":
1498
- v.extractorType = "header";
1499
- break;
1500
- case "path":
1501
- v.extractorType = "path";
1502
- break;
1503
- case "file":
1504
- v.extractorType = "file";
1505
- break;
1506
- case "custom":
1507
- v.extractorType = "custom";
1508
- break;
1509
- }
1510
- DefaultLogger.Debug(`Set extractorType ${v.extractorType} for param ${v.name}`);
1511
1473
  const validEntry = validData.find((it) => v.index === it.index && it.name === v.name);
1512
1474
  if (validEntry) {
1513
1475
  v.validRule = validEntry.rule;
@@ -1581,15 +1543,6 @@ function injectParamMetaData(app, target, options) {
1581
1543
  }
1582
1544
  }
1583
1545
  });
1584
- const fastPathScenario = detectFastPathScenario(data);
1585
- if (fastPathScenario) {
1586
- const fastPathHandler = createFastPathHandler(fastPathScenario, data);
1587
- if (fastPathHandler) {
1588
- DefaultLogger.Debug(`Created fast path handler for ${target.constructor.name}.${meta}, scenario: ${fastPathScenario}`);
1589
- data.fastPathHandler = fastPathHandler;
1590
- data.fastPathScenario = fastPathScenario;
1591
- }
1592
- }
1593
1546
  const hasAsyncParams = data.some(
1594
1547
  (p) => p.sourceType === "body" || p.sourceType === "file" || p.isDto
1595
1548
  // DTO validation might be async
@@ -1628,8 +1581,15 @@ var injectParam = /* @__PURE__ */ __name((fn, name, sourceType, paramName, defau
1628
1581
  };
1629
1582
  }, "injectParam");
1630
1583
  function getPublicMethods(classFilePath, className) {
1631
- const project = new Project();
1632
- const sourceFile = project.addSourceFileAtPath(classFilePath);
1584
+ const cacheKey = `${classFilePath}::${className}`;
1585
+ const cached = publicMethodsCache.get(cacheKey);
1586
+ if (cached) {
1587
+ return cached;
1588
+ }
1589
+ if (!cachedProject) {
1590
+ cachedProject = new Project();
1591
+ }
1592
+ const sourceFile = cachedProject.addSourceFileAtPath(classFilePath);
1633
1593
  const classDeclaration = sourceFile.getClass(className);
1634
1594
  const publicMethods = [];
1635
1595
  if (classDeclaration) {
@@ -1640,6 +1600,7 @@ function getPublicMethods(classFilePath, className) {
1640
1600
  }
1641
1601
  }
1642
1602
  }
1603
+ publicMethodsCache.set(cacheKey, publicMethods);
1643
1604
  return publicMethods;
1644
1605
  }
1645
1606
  __name(getPublicMethods, "getPublicMethods");
@@ -1647,104 +1608,6 @@ function getControllerPath(className) {
1647
1608
  return process.env.APP_PATH + "/controller/" + className + ".ts";
1648
1609
  }
1649
1610
  __name(getControllerPath, "getControllerPath");
1650
- function detectFastPathScenario(params) {
1651
- if (!params || params.length === 0) {
1652
- return null;
1653
- }
1654
- if (params.length === 1) {
1655
- const param = params[0];
1656
- const fnName = param.fn?.name || "";
1657
- if (fnName === "Get" && !param.validRule && !param.isDto) {
1658
- DefaultLogger.Debug(`Detected fast path scenario A: single query param without validation`);
1659
- return "SINGLE_QUERY_NO_VALIDATION";
1660
- }
1661
- if ((fnName === "Post" || fnName === "RequestBody") && param.isDto) {
1662
- DefaultLogger.Debug(`Detected fast path scenario B: single DTO from body`);
1663
- return "SINGLE_DTO_FROM_BODY";
1664
- }
1665
- }
1666
- if (params.length > 1) {
1667
- const allQueryNoValidation = params.every((param) => {
1668
- const fnName = param.fn?.name || "";
1669
- return fnName === "Get" && !param.validRule && !param.isDto;
1670
- });
1671
- if (allQueryNoValidation) {
1672
- DefaultLogger.Debug(`Detected fast path scenario C: multiple query params without validation`);
1673
- return "MULTIPLE_QUERY_NO_VALIDATION";
1674
- }
1675
- }
1676
- return null;
1677
- }
1678
- __name(detectFastPathScenario, "detectFastPathScenario");
1679
- function createFastPathHandler(scenario, params) {
1680
- switch (scenario) {
1681
- case "SINGLE_QUERY_NO_VALIDATION": {
1682
- const param = params[0];
1683
- const paramName = param.name;
1684
- const paramType = param.type;
1685
- if (paramName) {
1686
- return (ctx) => {
1687
- const value = ctx.query?.[paramName];
1688
- const converted = convertParamsType(value, paramType);
1689
- return [
1690
- converted
1691
- ];
1692
- };
1693
- } else {
1694
- return (ctx) => {
1695
- const query = ctx.query || {};
1696
- return [
1697
- query
1698
- ];
1699
- };
1700
- }
1701
- }
1702
- case "SINGLE_DTO_FROM_BODY": {
1703
- const param = params[0];
1704
- const clazz = param.clazz;
1705
- const dtoCheck = param.dtoCheck;
1706
- if (!clazz) {
1707
- DefaultLogger.Debug(`Cannot create fast path: DTO class not found`);
1708
- return null;
1709
- }
1710
- return async (ctx) => {
1711
- const body = await bodyParser(ctx, param.options);
1712
- let validatedValue;
1713
- if (dtoCheck) {
1714
- validatedValue = await ClassValidator.valid(clazz, body, true);
1715
- } else {
1716
- validatedValue = plainToClass(clazz, body, true);
1717
- }
1718
- return [
1719
- validatedValue
1720
- ];
1721
- };
1722
- }
1723
- case "MULTIPLE_QUERY_NO_VALIDATION": {
1724
- const paramConfigs = params.map((p) => ({
1725
- name: p.name,
1726
- type: p.type
1727
- }));
1728
- return (ctx) => {
1729
- const query = ctx.query || {};
1730
- const results = [];
1731
- for (const config of paramConfigs) {
1732
- if (config.name) {
1733
- const value = query[config.name];
1734
- const converted = convertParamsType(value, config.type);
1735
- results.push(converted);
1736
- } else {
1737
- results.push(query);
1738
- }
1739
- }
1740
- return results;
1741
- };
1742
- }
1743
- default:
1744
- return null;
1745
- }
1746
- }
1747
- __name(createFastPathHandler, "createFastPathHandler");
1748
1611
  function compileTypeConverter(type) {
1749
1612
  const normalizedType = type.toLowerCase();
1750
1613
  if (normalizedType === "string") {
@@ -1753,53 +1616,24 @@ function compileTypeConverter(type) {
1753
1616
  switch (normalizedType) {
1754
1617
  case "number":
1755
1618
  return (value) => {
1619
+ if (typeof value === "number") return value;
1756
1620
  if (value === null || value === void 0 || value === "") return value;
1757
- const num = Number(value);
1758
- return isNaN(num) ? value : num;
1621
+ return convertParamsType(value, "number");
1759
1622
  };
1760
1623
  case "boolean":
1761
1624
  return (value) => {
1762
- if (value === null || value === void 0) return value;
1763
1625
  if (typeof value === "boolean") return value;
1764
- if (typeof value === "string") {
1765
- const lower = value.toLowerCase();
1766
- if (lower === "true" || lower === "1") return true;
1767
- if (lower === "false" || lower === "0") return false;
1768
- }
1769
- return Boolean(value);
1626
+ return convertParamsType(value, "boolean");
1770
1627
  };
1771
1628
  case "array":
1772
1629
  return (value) => {
1773
- if (value === null || value === void 0) return value;
1774
1630
  if (Array.isArray(value)) return value;
1775
- if (typeof value === "string") {
1776
- try {
1777
- const parsed = JSON.parse(value);
1778
- return Array.isArray(parsed) ? parsed : [
1779
- value
1780
- ];
1781
- } catch {
1782
- return [
1783
- value
1784
- ];
1785
- }
1786
- }
1787
- return [
1788
- value
1789
- ];
1631
+ return convertParamsType(value, "array");
1790
1632
  };
1791
1633
  case "object":
1792
1634
  return (value) => {
1793
- if (value === null || value === void 0) return value;
1794
- if (typeof value === "object") return value;
1795
- if (typeof value === "string") {
1796
- try {
1797
- return JSON.parse(value);
1798
- } catch {
1799
- return value;
1800
- }
1801
- }
1802
- return value;
1635
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
1636
+ return convertParamsType(value, "object");
1803
1637
  };
1804
1638
  default:
1805
1639
  return (value) => convertParamsType(value, type);
@@ -1827,7 +1661,22 @@ function generatePrecompiledExtractor(param) {
1827
1661
  } else {
1828
1662
  return (ctx) => ctx.params || {};
1829
1663
  }
1830
- case "body":
1664
+ case "body": {
1665
+ const bodyOpts = param.options;
1666
+ if (paramName !== void 0) {
1667
+ return async (ctx) => {
1668
+ const parsed = await bodyParser(ctx, bodyOpts);
1669
+ return (parsed ?? {})[paramName];
1670
+ };
1671
+ } else {
1672
+ return async (ctx) => {
1673
+ const parsed = await bodyParser(ctx, bodyOpts);
1674
+ if (!parsed) return {};
1675
+ const { [FILE_KEY]: _files, ...bodyOnly } = parsed;
1676
+ return bodyOnly;
1677
+ };
1678
+ }
1679
+ }
1831
1680
  case "file":
1832
1681
  return null;
1833
1682
  case "custom":
@@ -1890,14 +1739,19 @@ function extractValueFromSource(source, param) {
1890
1739
  switch (param.sourceType) {
1891
1740
  case ParamSourceType.QUERY:
1892
1741
  return paramName ? source.query?.[paramName] : source.query;
1893
- case ParamSourceType.BODY:
1894
- return paramName ? source.body?.[paramName] : source.body;
1742
+ case ParamSourceType.BODY: {
1743
+ if (paramName) return source.body?.[paramName];
1744
+ const { [FILE_KEY]: _files, ...bodyFields } = source.body || {};
1745
+ return bodyFields;
1746
+ }
1895
1747
  case ParamSourceType.HEADER:
1896
1748
  return paramName ? source.headers?.[paramName] : source.headers;
1897
1749
  case ParamSourceType.PATH:
1898
1750
  return paramName ? source.params?.[paramName] : source.params;
1899
- case ParamSourceType.FILE:
1900
- return paramName ? source.body?.file?.[paramName] : source.body?.file;
1751
+ case ParamSourceType.FILE: {
1752
+ const files = source.body?.[FILE_KEY] || {};
1753
+ return paramName ? files[paramName] : files;
1754
+ }
1901
1755
  case ParamSourceType.CUSTOM:
1902
1756
  return null;
1903
1757
  default:
@@ -1909,21 +1763,18 @@ async function extractParamSources(ctx, params) {
1909
1763
  const needsBody = params.some((param) => {
1910
1764
  return param.sourceType === ParamSourceType.BODY || param.sourceType === ParamSourceType.FILE;
1911
1765
  });
1912
- const bodyData = {};
1766
+ let body = {};
1913
1767
  if (needsBody) {
1914
1768
  try {
1915
1769
  const parsedBody = await bodyParser(ctx, params[0]?.options);
1916
- bodyData.body = parsedBody;
1917
- if (typeof parsedBody === "object" && "file" in parsedBody) {
1918
- bodyData.file = parsedBody.file;
1919
- }
1770
+ body = parsedBody || {};
1920
1771
  } catch (err) {
1921
1772
  DefaultLogger.Error(`extractParamSources: Failed to parse body: ${err.message}`);
1922
1773
  }
1923
1774
  }
1924
1775
  return {
1925
1776
  query: ctx.query || {},
1926
- body: bodyData,
1777
+ body,
1927
1778
  params: ctx.params || {},
1928
1779
  headers: ctx.headers || {}
1929
1780
  };
@@ -1953,7 +1804,7 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
1953
1804
  }
1954
1805
  return validatedValue;
1955
1806
  } else {
1956
- const needsConversion = compiledTypeConverter !== null;
1807
+ const needsConversion = compiledTypeConverter != null;
1957
1808
  const needsValidation = !!(compiledValidator || opt.validRule);
1958
1809
  if (!needsConversion && !needsValidation) {
1959
1810
  return value;
@@ -1961,8 +1812,6 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
1961
1812
  let convertedValue = value;
1962
1813
  if (compiledTypeConverter) {
1963
1814
  convertedValue = compiledTypeConverter(value);
1964
- } else if (opt.type && opt.type !== "string") {
1965
- convertedValue = convertParamsType(value, opt.type);
1966
1815
  }
1967
1816
  if (compiledValidator) {
1968
1817
  compiledValidator(convertedValue);
@@ -2267,17 +2116,27 @@ var StrategyHandlerFactory = class {
2267
2116
  return converted;
2268
2117
  };
2269
2118
  });
2119
+ const allAsyncHaveExtractor = asyncParams.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
2270
2120
  return async (ctx) => {
2271
- const bodyData = await extractParamSources(ctx, params);
2272
- const asyncResults = asyncParams.map((p) => {
2273
- const rawValue = extractValueFromSource(bodyData, p);
2274
- if (rawValue === null && p.fn) {
2275
- return p.fn(ctx, p.options);
2276
- }
2121
+ const bodyData = allAsyncHaveExtractor ? null : await extractParamSources(ctx, params);
2122
+ const asyncResults = asyncParams.map(async (p) => {
2123
+ if (p.precompiledExtractor) {
2124
+ const rawValue2 = await p.precompiledExtractor(ctx);
2125
+ const value = rawValue2 === void 0 && p.defaultValue !== void 0 ? p.defaultValue : rawValue2;
2126
+ const paramOptions2 = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2127
+ return validateParam(app, ctx, value, paramOptions2, p.compiledValidator, p.compiledTypeConverter);
2128
+ }
2129
+ if (p.fn && typeof p.fn === "function") {
2130
+ const rawValue2 = await Promise.resolve(p.fn(ctx, p.options));
2131
+ const value = rawValue2 === void 0 && p.defaultValue !== void 0 ? p.defaultValue : rawValue2;
2132
+ const paramOptions2 = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2133
+ return validateParam(app, ctx, value, paramOptions2, p.compiledValidator, p.compiledTypeConverter);
2134
+ }
2135
+ const rawValue = bodyData ? extractValueFromSource(bodyData, p) : void 0;
2277
2136
  if (rawValue === void 0 && p.defaultValue !== void 0) {
2278
2137
  return p.defaultValue;
2279
2138
  }
2280
- const paramOptions = p.precompiledOptions || createParamOptions(p, 0);
2139
+ const paramOptions = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2281
2140
  return validateParam(app, ctx, rawValue, paramOptions, p.compiledValidator, p.compiledTypeConverter);
2282
2141
  });
2283
2142
  const syncResults = syncHandlers.map((h) => h(ctx));
@@ -2330,17 +2189,27 @@ var StrategyHandlerFactory = class {
2330
2189
  * Fallback: Generic async path
2331
2190
  */
2332
2191
  static createGenericAsyncHandler(params, app) {
2192
+ const allHaveExtractorOrFn = params.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
2333
2193
  return async (ctx) => {
2334
- const sources = await extractParamSources(ctx, params);
2335
- const paramPromises = params.map((v, k) => {
2336
- let rawValue = extractValueFromSource(sources, v);
2337
- if (rawValue === null && v.fn) {
2338
- rawValue = v.fn(ctx, v.options);
2339
- }
2194
+ const sources = allHaveExtractorOrFn ? null : await extractParamSources(ctx, params);
2195
+ const paramPromises = params.map(async (v, k) => {
2196
+ if (v.precompiledExtractor) {
2197
+ const rawValue2 = await v.precompiledExtractor(ctx);
2198
+ const value = rawValue2 === void 0 && v.defaultValue !== void 0 ? v.defaultValue : rawValue2;
2199
+ const paramOptions2 = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2200
+ return validateParam(app, ctx, value, paramOptions2, v.compiledValidator, v.compiledTypeConverter);
2201
+ }
2202
+ if (v.fn && typeof v.fn === "function") {
2203
+ const rawValue2 = await Promise.resolve(v.fn(ctx, v.options));
2204
+ const value = rawValue2 === void 0 && v.defaultValue !== void 0 ? v.defaultValue : rawValue2;
2205
+ const paramOptions2 = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2206
+ return validateParam(app, ctx, value, paramOptions2, v.compiledValidator, v.compiledTypeConverter);
2207
+ }
2208
+ let rawValue = sources ? extractValueFromSource(sources, v) : void 0;
2340
2209
  if (rawValue === void 0 && v.defaultValue !== void 0) {
2341
- rawValue = v.defaultValue;
2210
+ return v.defaultValue;
2342
2211
  }
2343
- const paramOptions = v.precompiledOptions || createParamOptions(v, k);
2212
+ const paramOptions = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2344
2213
  return validateParam(app, ctx, rawValue, paramOptions, v.compiledValidator, v.compiledTypeConverter);
2345
2214
  });
2346
2215
  return Promise.all(paramPromises);
@@ -2463,6 +2332,8 @@ function validateProtocolConfig(protocol, ext = {}, env = process.env.NODE_ENV |
2463
2332
  break;
2464
2333
  case "http":
2465
2334
  case "https":
2335
+ case "http2":
2336
+ case "http3":
2466
2337
  break;
2467
2338
  default:
2468
2339
  result.valid = false;
@@ -2494,7 +2365,7 @@ var GraphQLRouter = class {
2494
2365
  validation.warnings.forEach((warning) => DefaultLogger.Warn(`[GraphQLRouter] ${warning}`));
2495
2366
  }
2496
2367
  let schemaFilePath = extConfig.schemaFile;
2497
- if (schemaFilePath && !path.isAbsolute(schemaFilePath)) {
2368
+ if (schemaFilePath && !path.isAbsolute(schemaFilePath) && app.rootPath) {
2498
2369
  schemaFilePath = path.resolve(app.rootPath, schemaFilePath);
2499
2370
  }
2500
2371
  this.options = {
@@ -2551,7 +2422,19 @@ var GraphQLRouter = class {
2551
2422
  rootValue: routeHandler,
2552
2423
  validationRules: validationRules.length > 0 ? validationRules : void 0,
2553
2424
  formatError: this.options.ext?.debug ? void 0 : (error) => {
2554
- return new Error(error.message);
2425
+ const formatted = {
2426
+ message: error.message
2427
+ };
2428
+ if (error.extensions) {
2429
+ formatted.extensions = error.extensions;
2430
+ }
2431
+ if (error.locations) {
2432
+ formatted.locations = error.locations;
2433
+ }
2434
+ if (error.path) {
2435
+ formatted.path = error.path;
2436
+ }
2437
+ return formatted;
2555
2438
  },
2556
2439
  context: /* @__PURE__ */ __name((req) => {
2557
2440
  return req.koattyContext;
@@ -2609,6 +2492,13 @@ var GraphQLRouter = class {
2609
2492
  this.routerMap.set(name, impl);
2610
2493
  }
2611
2494
  /**
2495
+ * Escape string for safe JavaScript embedding
2496
+ * @private
2497
+ */
2498
+ escapeJsString(str) {
2499
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/</g, "\\x3c").replace(/>/g, "\\x3e").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
2500
+ }
2501
+ /**
2612
2502
  * Render GraphiQL interface
2613
2503
  *
2614
2504
  * @private
@@ -2616,6 +2506,7 @@ var GraphQLRouter = class {
2616
2506
  * @returns {string} HTML content for GraphiQL
2617
2507
  */
2618
2508
  renderGraphiQL(endpoint) {
2509
+ const safeEndpoint = this.escapeJsString(endpoint);
2619
2510
  return `<!DOCTYPE html>
2620
2511
  <html>
2621
2512
  <head>
@@ -2643,7 +2534,7 @@ var GraphQLRouter = class {
2643
2534
  ></script>
2644
2535
  <script>
2645
2536
  const fetcher = GraphiQL.createFetcher({
2646
- url: '${endpoint}',
2537
+ url: '${safeEndpoint}',
2647
2538
  });
2648
2539
  const root = ReactDOM.createRoot(document.getElementById('graphiql'));
2649
2540
  root.render(
@@ -2725,185 +2616,6 @@ var GraphQLRouter = class {
2725
2616
  DefaultLogger.Debug("GraphQL router cleanup completed");
2726
2617
  }
2727
2618
  };
2728
- var GrpcConnectionPool = class GrpcConnectionPool2 {
2729
- static {
2730
- __name(this, "GrpcConnectionPool");
2731
- }
2732
- pool;
2733
- maxSize;
2734
- constructor(maxSize = 10) {
2735
- this.pool = /* @__PURE__ */ new Map();
2736
- this.maxSize = maxSize;
2737
- }
2738
- /**
2739
- * Get connection from pool or create new one
2740
- */
2741
- get(serviceName, options) {
2742
- const connections = this.pool.get(serviceName);
2743
- if (connections && connections.length > 0) {
2744
- const conn = connections.pop();
2745
- DefaultLogger.Debug(`Reused connection from pool for service: ${serviceName}`);
2746
- return conn;
2747
- }
2748
- DefaultLogger.Debug(`Creating new connection for service: ${serviceName}`);
2749
- return this.create(serviceName, options);
2750
- }
2751
- /**
2752
- * Release connection back to pool
2753
- */
2754
- release(serviceName, connection) {
2755
- if (!connection) return;
2756
- if (!this.pool.has(serviceName)) {
2757
- this.pool.set(serviceName, []);
2758
- }
2759
- const connections = this.pool.get(serviceName);
2760
- if (connections.length < this.maxSize) {
2761
- connections.push(connection);
2762
- DefaultLogger.Debug(`Connection released back to pool for service: ${serviceName}, pool size: ${connections.length}`);
2763
- } else {
2764
- if (connection.close && typeof connection.close === "function") {
2765
- connection.close();
2766
- }
2767
- DefaultLogger.Debug(`Pool full for service: ${serviceName}, connection closed`);
2768
- }
2769
- }
2770
- /**
2771
- * Create new connection
2772
- * @param serviceName - Name of the gRPC service
2773
- * @param options - gRPC client options
2774
- * @returns Connection object (placeholder, actual implementation depends on gRPC client library)
2775
- */
2776
- create(serviceName, options) {
2777
- DefaultLogger.Debug(`Creating connection stub for service: ${serviceName}`);
2778
- return {
2779
- serviceName,
2780
- createdAt: Date.now(),
2781
- options,
2782
- // Placeholder methods
2783
- close: /* @__PURE__ */ __name(() => {
2784
- DefaultLogger.Debug(`Closing connection for service: ${serviceName}`);
2785
- }, "close")
2786
- };
2787
- }
2788
- /**
2789
- * Cleanup all connections in the pool
2790
- */
2791
- clear() {
2792
- let totalConnections = 0;
2793
- for (const [_serviceName, connections] of this.pool.entries()) {
2794
- for (const connection of connections) {
2795
- if (connection && connection.close && typeof connection.close === "function") {
2796
- connection.close();
2797
- }
2798
- totalConnections++;
2799
- }
2800
- }
2801
- this.pool.clear();
2802
- DefaultLogger.Info(`gRPC connection pool cleared, closed ${totalConnections} connections`);
2803
- }
2804
- /**
2805
- * Get pool statistics
2806
- */
2807
- getStats() {
2808
- const stats = {};
2809
- for (const [serviceName, connections] of this.pool.entries()) {
2810
- stats[serviceName] = connections.length;
2811
- }
2812
- return stats;
2813
- }
2814
- };
2815
- var GrpcBatchProcessor = class GrpcBatchProcessor2 {
2816
- static {
2817
- __name(this, "GrpcBatchProcessor");
2818
- }
2819
- batchSize;
2820
- batchQueue;
2821
- batchTimers;
2822
- constructor(batchSize = 10) {
2823
- this.batchSize = batchSize;
2824
- this.batchQueue = /* @__PURE__ */ new Map();
2825
- this.batchTimers = /* @__PURE__ */ new Map();
2826
- }
2827
- /**
2828
- * Add request to batch
2829
- */
2830
- addRequest(serviceName, request) {
2831
- return new Promise((resolve, reject) => {
2832
- if (!this.batchQueue.has(serviceName)) {
2833
- this.batchQueue.set(serviceName, []);
2834
- }
2835
- const queue = this.batchQueue.get(serviceName);
2836
- queue.push({
2837
- request,
2838
- resolve,
2839
- reject
2840
- });
2841
- if (queue.length >= this.batchSize) {
2842
- this.processBatch(serviceName);
2843
- } else if (!this.batchTimers.has(serviceName)) {
2844
- this.batchTimers.set(serviceName, setTimeout(() => {
2845
- this.processBatch(serviceName);
2846
- }, 100));
2847
- }
2848
- });
2849
- }
2850
- /**
2851
- * Process batch of requests
2852
- */
2853
- processBatch(serviceName) {
2854
- const queue = this.batchQueue.get(serviceName);
2855
- if (!queue || queue.length === 0) return;
2856
- const timer = this.batchTimers.get(serviceName);
2857
- if (timer) {
2858
- clearTimeout(timer);
2859
- this.batchTimers.delete(serviceName);
2860
- }
2861
- DefaultLogger.Debug(`Processing batch for service ${serviceName}, ${queue.length} requests`);
2862
- queue.forEach((item, index) => {
2863
- try {
2864
- item.resolve({
2865
- success: true,
2866
- data: item.request,
2867
- batchIndex: index,
2868
- batchSize: queue.length
2869
- });
2870
- } catch (error) {
2871
- item.reject(error);
2872
- }
2873
- });
2874
- this.batchQueue.delete(serviceName);
2875
- DefaultLogger.Debug(`Batch processing completed for service ${serviceName}`);
2876
- }
2877
- /**
2878
- * Flush all pending batches and cleanup
2879
- */
2880
- flush() {
2881
- let totalProcessed = 0;
2882
- for (const serviceName of this.batchQueue.keys()) {
2883
- const queueSize = this.batchQueue.get(serviceName)?.length || 0;
2884
- this.processBatch(serviceName);
2885
- totalProcessed += queueSize;
2886
- }
2887
- for (const timer of this.batchTimers.values()) {
2888
- clearTimeout(timer);
2889
- }
2890
- this.batchTimers.clear();
2891
- DefaultLogger.Info(`gRPC batch processor flushed, processed ${totalProcessed} pending requests`);
2892
- }
2893
- /**
2894
- * Get batch queue statistics
2895
- */
2896
- getStats() {
2897
- const stats = [];
2898
- for (const [serviceName, queue] of this.batchQueue.entries()) {
2899
- stats.push({
2900
- serviceName,
2901
- queueSize: queue.length
2902
- });
2903
- }
2904
- return stats;
2905
- }
2906
- };
2907
2619
  var StreamManager = class StreamManager2 {
2908
2620
  static {
2909
2621
  __name(this, "StreamManager");
@@ -2916,7 +2628,6 @@ var StreamManager = class StreamManager2 {
2916
2628
  maxConcurrentStreams: config.maxConcurrentStreams || 100,
2917
2629
  streamTimeout: config.streamTimeout || 3e5,
2918
2630
  backpressureThreshold: config.backpressureThreshold || 1e3,
2919
- bufferSize: config.bufferSize || 64 * 1024,
2920
2631
  ...config
2921
2632
  };
2922
2633
  }
@@ -2937,6 +2648,18 @@ var StreamManager = class StreamManager2 {
2937
2648
  return state;
2938
2649
  }
2939
2650
  /**
2651
+ * 移除流
2652
+ */
2653
+ removeStream(id) {
2654
+ this.streams.delete(id);
2655
+ }
2656
+ /**
2657
+ * 获取流状态
2658
+ */
2659
+ getStreamState(id) {
2660
+ return this.streams.get(id);
2661
+ }
2662
+ /**
2940
2663
  * 更新流状态
2941
2664
  */
2942
2665
  updateStream(id, updates) {
@@ -2946,12 +2669,6 @@ var StreamManager = class StreamManager2 {
2946
2669
  }
2947
2670
  }
2948
2671
  /**
2949
- * 移除流
2950
- */
2951
- removeStream(id) {
2952
- this.streams.delete(id);
2953
- }
2954
- /**
2955
2672
  * 检查是否达到背压阈值
2956
2673
  */
2957
2674
  isBackpressureTriggered(id) {
@@ -3001,8 +2718,6 @@ var GrpcRouter = class {
3001
2718
  protocol;
3002
2719
  options;
3003
2720
  router;
3004
- connectionPool;
3005
- batchProcessor;
3006
2721
  streamManager;
3007
2722
  constructor(app, options = {
3008
2723
  protocol: "grpc",
@@ -3017,20 +2732,16 @@ var GrpcRouter = class {
3017
2732
  validation.warnings.forEach((warning) => DefaultLogger.Warn(`[GrpcRouter] ${warning}`));
3018
2733
  }
3019
2734
  let protoFilePath = extConfig.protoFile;
3020
- if (protoFilePath && !path.isAbsolute(protoFilePath)) {
2735
+ if (protoFilePath && !path.isAbsolute(protoFilePath) && app.rootPath) {
3021
2736
  protoFilePath = path.resolve(app.rootPath, protoFilePath);
3022
2737
  }
3023
2738
  this.options = {
3024
2739
  ...options,
3025
2740
  protoFile: protoFilePath,
3026
- poolSize: extConfig.poolSize || 10,
3027
- batchSize: extConfig.batchSize || 10,
3028
2741
  streamConfig: extConfig.streamConfig || {}
3029
2742
  };
3030
2743
  this.protocol = options.protocol || "grpc";
3031
2744
  this.router = /* @__PURE__ */ new Map();
3032
- this.connectionPool = new GrpcConnectionPool(this.options.poolSize);
3033
- this.batchProcessor = new GrpcBatchProcessor(this.options.batchSize);
3034
2745
  this.streamManager = new StreamManager(this.options.streamConfig);
3035
2746
  }
3036
2747
  /**
@@ -3103,7 +2814,7 @@ var GrpcRouter = class {
3103
2814
  */
3104
2815
  async handleServerStreaming(call, app, ctlItem) {
3105
2816
  const streamId = `server_${Date.now()}_${Math.random()}`;
3106
- const streamState = this.streamManager.registerStream(streamId, "server_streaming");
2817
+ this.streamManager.registerStream(streamId, "server_streaming");
3107
2818
  try {
3108
2819
  DefaultLogger.Debug(`[GRPC_ROUTER] Handling server streaming call for ${ctlItem.name}.${ctlItem.method}`);
3109
2820
  const timeout = setTimeout(() => {
@@ -3128,9 +2839,10 @@ var GrpcRouter = class {
3128
2839
  return false;
3129
2840
  }
3130
2841
  call.write(data);
3131
- this.streamManager.updateStream(streamId, {
3132
- messageCount: streamState.messageCount + 1
3133
- });
2842
+ const currentState = this.streamManager.getStreamState(streamId);
2843
+ if (currentState) {
2844
+ currentState.messageCount++;
2845
+ }
3134
2846
  return true;
3135
2847
  };
3136
2848
  ctx.endStream = () => {
@@ -3153,7 +2865,7 @@ var GrpcRouter = class {
3153
2865
  */
3154
2866
  handleClientStreaming(call, callback, app, ctlItem) {
3155
2867
  const streamId = `client_${Date.now()}_${Math.random()}`;
3156
- const streamState = this.streamManager.registerStream(streamId, "client_streaming");
2868
+ this.streamManager.registerStream(streamId, "client_streaming");
3157
2869
  const messages = [];
3158
2870
  try {
3159
2871
  DefaultLogger.Debug(`[GRPC_ROUTER] Handling client streaming call for ${ctlItem.name}.${ctlItem.method}`);
@@ -3164,10 +2876,11 @@ var GrpcRouter = class {
3164
2876
  }, this.options.streamConfig?.streamTimeout || 3e5);
3165
2877
  call.on("data", (data) => {
3166
2878
  messages.push(data);
3167
- this.streamManager.updateStream(streamId, {
3168
- messageCount: streamState.messageCount + 1,
3169
- bufferSize: streamState.bufferSize + JSON.stringify(data).length
3170
- });
2879
+ const currentState = this.streamManager.getStreamState(streamId);
2880
+ if (currentState) {
2881
+ currentState.messageCount++;
2882
+ currentState.bufferSize += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(JSON.stringify(data));
2883
+ }
3171
2884
  if (this.streamManager.isBackpressureTriggered(streamId)) {
3172
2885
  DefaultLogger.Warn(`[GRPC_ROUTER] Backpressure triggered for client stream ${streamId}`);
3173
2886
  call.pause();
@@ -3215,7 +2928,7 @@ var GrpcRouter = class {
3215
2928
  */
3216
2929
  handleBidirectionalStreaming(call, app, ctlItem) {
3217
2930
  const streamId = `bidi_${Date.now()}_${Math.random()}`;
3218
- const streamState = this.streamManager.registerStream(streamId, "bidirectional_streaming");
2931
+ this.streamManager.registerStream(streamId, "bidirectional_streaming");
3219
2932
  try {
3220
2933
  DefaultLogger.Debug(`[GRPC_ROUTER] Handling bidirectional streaming call for ${ctlItem.name}.${ctlItem.method}`);
3221
2934
  const timeout = setTimeout(() => {
@@ -3224,10 +2937,11 @@ var GrpcRouter = class {
3224
2937
  this.streamManager.removeStream(streamId);
3225
2938
  }, this.options.streamConfig?.streamTimeout || 3e5);
3226
2939
  call.on("data", async (data) => {
3227
- this.streamManager.updateStream(streamId, {
3228
- messageCount: streamState.messageCount + 1,
3229
- bufferSize: streamState.bufferSize + JSON.stringify(data).length
3230
- });
2940
+ const currentState = this.streamManager.getStreamState(streamId);
2941
+ if (currentState) {
2942
+ currentState.messageCount++;
2943
+ currentState.bufferSize += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(JSON.stringify(data));
2944
+ }
3231
2945
  if (this.streamManager.isBackpressureTriggered(streamId)) {
3232
2946
  DefaultLogger.Warn(`[GRPC_ROUTER] Backpressure triggered for bidirectional stream ${streamId}`);
3233
2947
  call.pause();
@@ -3364,8 +3078,12 @@ var GrpcRouter = class {
3364
3078
  }
3365
3079
  DefaultLogger.Debug(`[GRPC_ROUTER] \u2705 Register request mapping: ["${path3}" => ${ctlItem.name}.${ctlItem.method}]`);
3366
3080
  impl[handler.name] = (call, callback) => {
3367
- DefaultLogger.Warn(`[GRPC_ROUTER] \u26A0\uFE0F Placeholder handler called for: ${handler.name} - this should not happen!`);
3368
- callback(new Error("This handler should not be called - routing should go through middleware"));
3081
+ const methodPath = `${si.name}/${handler.name}`;
3082
+ DefaultLogger.Warn(`[GRPC_ROUTER] Placeholder handler invoked for ${methodPath}. No controller matched this gRPC method.`);
3083
+ callback({
3084
+ code: 12,
3085
+ message: `Method ${methodPath} not implemented. Ensure a controller with @GrpcMethod('${handler.name}') is registered.`
3086
+ });
3369
3087
  };
3370
3088
  }
3371
3089
  if (Object.keys(impl).length > 0) {
@@ -3450,8 +3168,6 @@ var GrpcRouter = class {
3450
3168
  cleanup() {
3451
3169
  DefaultLogger.Info("Starting gRPC router cleanup...");
3452
3170
  this.streamManager.closeAllStreams();
3453
- this.batchProcessor.flush();
3454
- this.connectionPool.clear();
3455
3171
  DefaultLogger.Info("gRPC router cleanup completed");
3456
3172
  }
3457
3173
  };
@@ -3486,7 +3202,7 @@ var HttpRouter = class {
3486
3202
  * @returns
3487
3203
  */
3488
3204
  SetRouter(name, impl) {
3489
- if (Helper5.isEmpty(impl.path)) return;
3205
+ if (!impl || Helper5.isEmpty(impl.path)) return;
3490
3206
  const method = (impl.method || "").toLowerCase();
3491
3207
  const routeHandler = impl.implementation;
3492
3208
  if ([
@@ -3701,7 +3417,7 @@ var WebsocketRouter = class {
3701
3417
  * @returns
3702
3418
  */
3703
3419
  SetRouter(name, impl) {
3704
- if (Helper.isEmpty(impl.path)) return;
3420
+ if (!impl || Helper.isEmpty(impl.path)) return;
3705
3421
  const routeHandler = impl.implementation;
3706
3422
  this.router.get(impl.path, routeHandler);
3707
3423
  this.routerMap.set(name, impl);
@@ -3940,6 +3656,10 @@ var RouterFactory = class _RouterFactory {
3940
3656
  * Create router instance
3941
3657
  */
3942
3658
  create(protocol, app, options) {
3659
+ if (this.hasShutdown) {
3660
+ DefaultLogger.Debug("RouterFactory: Resetting shutdown state for new router creation");
3661
+ this.hasShutdown = false;
3662
+ }
3943
3663
  const normalizedProtocol = protocol.toLowerCase();
3944
3664
  const RouterClass = this.routerRegistry.get(normalizedProtocol);
3945
3665
  if (!RouterClass) {
@@ -4160,17 +3880,28 @@ function Get(name, defaultValue) {
4160
3880
  }
4161
3881
  __name(Get, "Get");
4162
3882
  function Post(name, defaultValue) {
4163
- return injectParam(async (ctx, opt) => {
4164
- const data = await bodyParser(ctx, opt);
4165
- const params = data.body ?? data;
4166
- return name ? params[name] : params;
4167
- }, "Post", ParamSourceType.BODY, name, defaultValue);
3883
+ return injectParam(
3884
+ // NOTE: This fn is a fallback path only. At runtime the strategy extractor uses
3885
+ // param.precompiledExtractor (generated by generatePrecompiledExtractor for BODY type)
3886
+ // which bypasses this fn entirely. This fn is invoked only when precompiledExtractor
3887
+ // is unavailable (e.g. startup compilation failure or CUSTOM source type fallback).
3888
+ async (ctx, opt) => {
3889
+ const data = await bodyParser(ctx, opt);
3890
+ if (name) return data[name];
3891
+ const { [FILE_KEY]: _files, ...bodyOnly } = data || {};
3892
+ return bodyOnly;
3893
+ },
3894
+ "Post",
3895
+ ParamSourceType.BODY,
3896
+ name,
3897
+ defaultValue
3898
+ );
4168
3899
  }
4169
3900
  __name(Post, "Post");
4170
3901
  function File(name, defaultValue) {
4171
3902
  return injectParam(async (ctx, opt) => {
4172
3903
  const body = await bodyParser(ctx, opt);
4173
- const params = body.file ?? {};
3904
+ const params = body[FILE_KEY] ?? {};
4174
3905
  return name ? params[name] : params;
4175
3906
  }, "File", ParamSourceType.FILE, name, defaultValue);
4176
3907
  }