koatty_router 2.1.10 → 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-08 04:45:07
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/
@@ -789,6 +789,9 @@ function RegisterMiddleware(app, config) {
789
789
  };
790
790
  }
791
791
  __name(RegisterMiddleware, "RegisterMiddleware");
792
+
793
+ // src/payload/interface.ts
794
+ var FILE_KEY = /* @__PURE__ */ Symbol.for("koatty.files");
792
795
  function parseText(ctx, opts) {
793
796
  return getRawBody__default.default(inflate__default.default(ctx.req), opts).catch((err) => {
794
797
  koatty_logger.DefaultLogger.Error(err);
@@ -800,8 +803,9 @@ async function parseJson(ctx, opts) {
800
803
  const str = await parseText(ctx, opts);
801
804
  if (!str) return {};
802
805
  try {
803
- return {
804
- body: JSON.parse(str)
806
+ const parsed = JSON.parse(str);
807
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
808
+ value: parsed
805
809
  };
806
810
  } catch (error) {
807
811
  koatty_logger.DefaultLogger.Error(error);
@@ -810,18 +814,13 @@ async function parseJson(ctx, opts) {
810
814
  }
811
815
  __name(parseJson, "parseJson");
812
816
  async function parseForm(ctx, opts) {
813
- if (!ctx.request.headers["content-length"] || !ctx.request.headers["content-type"]?.includes("application/x-www-form-urlencoded")) {
814
- return {};
815
- }
816
817
  const str = await parseText(ctx, opts);
817
818
  if (!str || str.trim().length === 0) {
818
819
  return {};
819
820
  }
820
821
  try {
821
822
  const result = fastQuerystring.parse(str);
822
- return {
823
- body: result
824
- };
823
+ return result;
825
824
  } catch (error) {
826
825
  koatty_logger.DefaultLogger.Error("[FormParseError]", error);
827
826
  return {};
@@ -853,12 +852,6 @@ __name(deleteFiles, "deleteFiles");
853
852
 
854
853
  // src/payload/parser/multipart.ts
855
854
  function parseMultipart(ctx, opts) {
856
- if (!ctx.request.headers["content-type"]?.includes("multipart/form-data")) {
857
- return Promise.resolve({
858
- body: {},
859
- file: {}
860
- });
861
- }
862
855
  const form = new formidable.IncomingForm({
863
856
  encoding: opts.encoding,
864
857
  multiples: opts.multiples,
@@ -881,15 +874,12 @@ function parseMultipart(ctx, opts) {
881
874
  if (err) {
882
875
  cleanup();
883
876
  koatty_logger.DefaultLogger.Error("[MultipartParseError]", err);
884
- return resolve({
885
- body: {},
886
- file: {}
887
- });
877
+ return resolve({});
888
878
  }
889
879
  uploadFiles = files;
890
880
  resolve({
891
- body: fields,
892
- file: files
881
+ ...fields,
882
+ [FILE_KEY]: files
893
883
  });
894
884
  });
895
885
  });
@@ -903,8 +893,9 @@ async function parseXml(ctx, opts) {
903
893
  const str = await parseText(ctx, opts);
904
894
  if (!str) return {};
905
895
  try {
906
- return {
907
- body: xmlParser.parse(str)
896
+ const parsed = xmlParser.parse(str);
897
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
898
+ value: parsed
908
899
  };
909
900
  } catch (error) {
910
901
  koatty_logger.DefaultLogger.Error(error);
@@ -945,11 +936,14 @@ async function parseWebSocket(ctx, opts) {
945
936
  const str = await parseText(ctx, opts);
946
937
  if (!str) return {};
947
938
  try {
948
- return {
949
- body: JSON.parse(str)
939
+ const parsed = JSON.parse(str);
940
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
941
+ value: parsed
950
942
  };
951
943
  } catch {
952
- return str;
944
+ return {
945
+ value: str
946
+ };
953
947
  }
954
948
  } catch (error) {
955
949
  koatty_logger.DefaultLogger.Error("[WebSocketParseError]", error);
@@ -1169,6 +1163,7 @@ var PayloadCacheManager = class _PayloadCacheManager {
1169
1163
  var cacheManager = PayloadCacheManager.getInstance();
1170
1164
 
1171
1165
  // src/payload/payload.ts
1166
+ var bodyCache = /* @__PURE__ */ new WeakMap();
1172
1167
  var supportedMethods = /* @__PURE__ */ new Set([
1173
1168
  "POST",
1174
1169
  "PUT",
@@ -1226,7 +1221,7 @@ function payload(options) {
1226
1221
  if (!Object.prototype.hasOwnProperty.call(ctx, "requestFile")) {
1227
1222
  Helper5.Helper.define(ctx, "requestFile", async (name) => {
1228
1223
  const body = await bodyParser(ctx, opts);
1229
- const files = body.file ?? {};
1224
+ const files = body[FILE_KEY] ?? {};
1230
1225
  return name ? files[name] : files;
1231
1226
  });
1232
1227
  }
@@ -1246,22 +1241,32 @@ function queryParser(ctx, _options) {
1246
1241
  return Object.assign({}, query, params);
1247
1242
  }
1248
1243
  __name(queryParser, "queryParser");
1249
- 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) {
1250
1251
  try {
1251
- let body = ctx.getMetaData("_body")[0];
1252
- if (!Helper5.Helper.isEmpty(body)) {
1253
- return body;
1254
- }
1252
+ const cached = bodyCache.get(ctx);
1253
+ if (cached !== void 0) return cached;
1255
1254
  const opts = cacheManager.getMergedOptions(options);
1256
- body = await parseBody(ctx, opts);
1257
- 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
+ }
1258
1261
  return body;
1259
1262
  } catch (err) {
1260
1263
  koatty_logger.DefaultLogger.Error(err);
1261
- return {};
1264
+ const empty = {};
1265
+ bodyCache.set(ctx, empty);
1266
+ return empty;
1262
1267
  }
1263
1268
  }
1264
- __name(bodyParser, "bodyParser");
1269
+ __name(parseBodyAndCache, "parseBodyAndCache");
1265
1270
  function parseBody(ctx, options) {
1266
1271
  if (!isSupportedMethod(ctx.method)) {
1267
1272
  return Promise.resolve({});
@@ -1495,27 +1500,6 @@ function injectParamMetaData(app, target, options) {
1495
1500
  if (!v.sourceType) {
1496
1501
  v.sourceType = "custom";
1497
1502
  }
1498
- switch (v.sourceType) {
1499
- case "query":
1500
- v.extractorType = "query";
1501
- break;
1502
- case "body":
1503
- v.extractorType = "body";
1504
- break;
1505
- case "header":
1506
- v.extractorType = "header";
1507
- break;
1508
- case "path":
1509
- v.extractorType = "path";
1510
- break;
1511
- case "file":
1512
- v.extractorType = "file";
1513
- break;
1514
- case "custom":
1515
- v.extractorType = "custom";
1516
- break;
1517
- }
1518
- koatty_logger.DefaultLogger.Debug(`Set extractorType ${v.extractorType} for param ${v.name}`);
1519
1503
  const validEntry = validData.find((it) => v.index === it.index && it.name === v.name);
1520
1504
  if (validEntry) {
1521
1505
  v.validRule = validEntry.rule;
@@ -1589,15 +1573,6 @@ function injectParamMetaData(app, target, options) {
1589
1573
  }
1590
1574
  }
1591
1575
  });
1592
- const fastPathScenario = detectFastPathScenario(data);
1593
- if (fastPathScenario) {
1594
- const fastPathHandler = createFastPathHandler(fastPathScenario, data);
1595
- if (fastPathHandler) {
1596
- koatty_logger.DefaultLogger.Debug(`Created fast path handler for ${target.constructor.name}.${meta}, scenario: ${fastPathScenario}`);
1597
- data.fastPathHandler = fastPathHandler;
1598
- data.fastPathScenario = fastPathScenario;
1599
- }
1600
- }
1601
1576
  const hasAsyncParams = data.some(
1602
1577
  (p) => p.sourceType === "body" || p.sourceType === "file" || p.isDto
1603
1578
  // DTO validation might be async
@@ -1663,104 +1638,6 @@ function getControllerPath(className) {
1663
1638
  return process.env.APP_PATH + "/controller/" + className + ".ts";
1664
1639
  }
1665
1640
  __name(getControllerPath, "getControllerPath");
1666
- function detectFastPathScenario(params) {
1667
- if (!params || params.length === 0) {
1668
- return null;
1669
- }
1670
- if (params.length === 1) {
1671
- const param = params[0];
1672
- const fnName = param.fn?.name || "";
1673
- if (fnName === "Get" && !param.validRule && !param.isDto) {
1674
- koatty_logger.DefaultLogger.Debug(`Detected fast path scenario A: single query param without validation`);
1675
- return "SINGLE_QUERY_NO_VALIDATION";
1676
- }
1677
- if ((fnName === "Post" || fnName === "RequestBody") && param.isDto) {
1678
- koatty_logger.DefaultLogger.Debug(`Detected fast path scenario B: single DTO from body`);
1679
- return "SINGLE_DTO_FROM_BODY";
1680
- }
1681
- }
1682
- if (params.length > 1) {
1683
- const allQueryNoValidation = params.every((param) => {
1684
- const fnName = param.fn?.name || "";
1685
- return fnName === "Get" && !param.validRule && !param.isDto;
1686
- });
1687
- if (allQueryNoValidation) {
1688
- koatty_logger.DefaultLogger.Debug(`Detected fast path scenario C: multiple query params without validation`);
1689
- return "MULTIPLE_QUERY_NO_VALIDATION";
1690
- }
1691
- }
1692
- return null;
1693
- }
1694
- __name(detectFastPathScenario, "detectFastPathScenario");
1695
- function createFastPathHandler(scenario, params) {
1696
- switch (scenario) {
1697
- case "SINGLE_QUERY_NO_VALIDATION": {
1698
- const param = params[0];
1699
- const paramName = param.name;
1700
- const paramType = param.type;
1701
- if (paramName) {
1702
- return (ctx) => {
1703
- const value = ctx.query?.[paramName];
1704
- const converted = koatty_validation.convertParamsType(value, paramType);
1705
- return [
1706
- converted
1707
- ];
1708
- };
1709
- } else {
1710
- return (ctx) => {
1711
- const query = ctx.query || {};
1712
- return [
1713
- query
1714
- ];
1715
- };
1716
- }
1717
- }
1718
- case "SINGLE_DTO_FROM_BODY": {
1719
- const param = params[0];
1720
- const clazz = param.clazz;
1721
- const dtoCheck = param.dtoCheck;
1722
- if (!clazz) {
1723
- koatty_logger.DefaultLogger.Debug(`Cannot create fast path: DTO class not found`);
1724
- return null;
1725
- }
1726
- return async (ctx) => {
1727
- const body = await bodyParser(ctx, param.options);
1728
- let validatedValue;
1729
- if (dtoCheck) {
1730
- validatedValue = await koatty_validation.ClassValidator.valid(clazz, body, true);
1731
- } else {
1732
- validatedValue = koatty_validation.plainToClass(clazz, body, true);
1733
- }
1734
- return [
1735
- validatedValue
1736
- ];
1737
- };
1738
- }
1739
- case "MULTIPLE_QUERY_NO_VALIDATION": {
1740
- const paramConfigs = params.map((p) => ({
1741
- name: p.name,
1742
- type: p.type
1743
- }));
1744
- return (ctx) => {
1745
- const query = ctx.query || {};
1746
- const results = [];
1747
- for (const config of paramConfigs) {
1748
- if (config.name) {
1749
- const value = query[config.name];
1750
- const converted = koatty_validation.convertParamsType(value, config.type);
1751
- results.push(converted);
1752
- } else {
1753
- results.push(query);
1754
- }
1755
- }
1756
- return results;
1757
- };
1758
- }
1759
- default:
1760
- return null;
1761
- }
1762
- }
1763
- __name(createFastPathHandler, "createFastPathHandler");
1764
1641
  function compileTypeConverter(type) {
1765
1642
  const normalizedType = type.toLowerCase();
1766
1643
  if (normalizedType === "string") {
@@ -1769,53 +1646,24 @@ function compileTypeConverter(type) {
1769
1646
  switch (normalizedType) {
1770
1647
  case "number":
1771
1648
  return (value) => {
1649
+ if (typeof value === "number") return value;
1772
1650
  if (value === null || value === void 0 || value === "") return value;
1773
- const num = Number(value);
1774
- return isNaN(num) ? value : num;
1651
+ return koatty_validation.convertParamsType(value, "number");
1775
1652
  };
1776
1653
  case "boolean":
1777
1654
  return (value) => {
1778
- if (value === null || value === void 0) return value;
1779
1655
  if (typeof value === "boolean") return value;
1780
- if (typeof value === "string") {
1781
- const lower = value.toLowerCase();
1782
- if (lower === "true" || lower === "1") return true;
1783
- if (lower === "false" || lower === "0") return false;
1784
- }
1785
- return Boolean(value);
1656
+ return koatty_validation.convertParamsType(value, "boolean");
1786
1657
  };
1787
1658
  case "array":
1788
1659
  return (value) => {
1789
- if (value === null || value === void 0) return value;
1790
1660
  if (Array.isArray(value)) return value;
1791
- if (typeof value === "string") {
1792
- try {
1793
- const parsed = JSON.parse(value);
1794
- return Array.isArray(parsed) ? parsed : [
1795
- value
1796
- ];
1797
- } catch {
1798
- return [
1799
- value
1800
- ];
1801
- }
1802
- }
1803
- return [
1804
- value
1805
- ];
1661
+ return koatty_validation.convertParamsType(value, "array");
1806
1662
  };
1807
1663
  case "object":
1808
1664
  return (value) => {
1809
- if (value === null || value === void 0) return value;
1810
- if (typeof value === "object") return value;
1811
- if (typeof value === "string") {
1812
- try {
1813
- return JSON.parse(value);
1814
- } catch {
1815
- return value;
1816
- }
1817
- }
1818
- return value;
1665
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
1666
+ return koatty_validation.convertParamsType(value, "object");
1819
1667
  };
1820
1668
  default:
1821
1669
  return (value) => koatty_validation.convertParamsType(value, type);
@@ -1843,7 +1691,22 @@ function generatePrecompiledExtractor(param) {
1843
1691
  } else {
1844
1692
  return (ctx) => ctx.params || {};
1845
1693
  }
1846
- 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
+ }
1847
1710
  case "file":
1848
1711
  return null;
1849
1712
  case "custom":
@@ -1906,14 +1769,19 @@ function extractValueFromSource(source, param) {
1906
1769
  switch (param.sourceType) {
1907
1770
  case ParamSourceType.QUERY:
1908
1771
  return paramName ? source.query?.[paramName] : source.query;
1909
- case ParamSourceType.BODY:
1910
- 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
+ }
1911
1777
  case ParamSourceType.HEADER:
1912
1778
  return paramName ? source.headers?.[paramName] : source.headers;
1913
1779
  case ParamSourceType.PATH:
1914
1780
  return paramName ? source.params?.[paramName] : source.params;
1915
- case ParamSourceType.FILE:
1916
- 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
+ }
1917
1785
  case ParamSourceType.CUSTOM:
1918
1786
  return null;
1919
1787
  default:
@@ -1925,21 +1793,18 @@ async function extractParamSources(ctx, params) {
1925
1793
  const needsBody = params.some((param) => {
1926
1794
  return param.sourceType === ParamSourceType.BODY || param.sourceType === ParamSourceType.FILE;
1927
1795
  });
1928
- const bodyData = {};
1796
+ let body = {};
1929
1797
  if (needsBody) {
1930
1798
  try {
1931
1799
  const parsedBody = await bodyParser(ctx, params[0]?.options);
1932
- bodyData.body = parsedBody;
1933
- if (typeof parsedBody === "object" && "file" in parsedBody) {
1934
- bodyData.file = parsedBody.file;
1935
- }
1800
+ body = parsedBody || {};
1936
1801
  } catch (err) {
1937
1802
  koatty_logger.DefaultLogger.Error(`extractParamSources: Failed to parse body: ${err.message}`);
1938
1803
  }
1939
1804
  }
1940
1805
  return {
1941
1806
  query: ctx.query || {},
1942
- body: bodyData,
1807
+ body,
1943
1808
  params: ctx.params || {},
1944
1809
  headers: ctx.headers || {}
1945
1810
  };
@@ -1969,7 +1834,7 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
1969
1834
  }
1970
1835
  return validatedValue;
1971
1836
  } else {
1972
- const needsConversion = compiledTypeConverter !== null;
1837
+ const needsConversion = compiledTypeConverter != null;
1973
1838
  const needsValidation = !!(compiledValidator || opt.validRule);
1974
1839
  if (!needsConversion && !needsValidation) {
1975
1840
  return value;
@@ -1977,8 +1842,6 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
1977
1842
  let convertedValue = value;
1978
1843
  if (compiledTypeConverter) {
1979
1844
  convertedValue = compiledTypeConverter(value);
1980
- } else if (opt.type && opt.type !== "string") {
1981
- convertedValue = koatty_validation.convertParamsType(value, opt.type);
1982
1845
  }
1983
1846
  if (compiledValidator) {
1984
1847
  compiledValidator(convertedValue);
@@ -2283,17 +2146,27 @@ var StrategyHandlerFactory = class {
2283
2146
  return converted;
2284
2147
  };
2285
2148
  });
2149
+ const allAsyncHaveExtractor = asyncParams.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
2286
2150
  return async (ctx) => {
2287
- const bodyData = await extractParamSources(ctx, params);
2288
- const asyncResults = asyncParams.map((p) => {
2289
- const rawValue = extractValueFromSource(bodyData, p);
2290
- if (rawValue === null && p.fn) {
2291
- 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);
2292
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);
2164
+ }
2165
+ const rawValue = bodyData ? extractValueFromSource(bodyData, p) : void 0;
2293
2166
  if (rawValue === void 0 && p.defaultValue !== void 0) {
2294
2167
  return p.defaultValue;
2295
2168
  }
2296
- const paramOptions = p.precompiledOptions || createParamOptions(p, 0);
2169
+ const paramOptions = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
2297
2170
  return validateParam(app, ctx, rawValue, paramOptions, p.compiledValidator, p.compiledTypeConverter);
2298
2171
  });
2299
2172
  const syncResults = syncHandlers.map((h) => h(ctx));
@@ -2346,17 +2219,27 @@ var StrategyHandlerFactory = class {
2346
2219
  * Fallback: Generic async path
2347
2220
  */
2348
2221
  static createGenericAsyncHandler(params, app) {
2222
+ const allHaveExtractorOrFn = params.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
2349
2223
  return async (ctx) => {
2350
- const sources = await extractParamSources(ctx, params);
2351
- const paramPromises = params.map((v, k) => {
2352
- let rawValue = extractValueFromSource(sources, v);
2353
- if (rawValue === null && v.fn) {
2354
- 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);
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);
2355
2237
  }
2238
+ let rawValue = sources ? extractValueFromSource(sources, v) : void 0;
2356
2239
  if (rawValue === void 0 && v.defaultValue !== void 0) {
2357
- rawValue = v.defaultValue;
2240
+ return v.defaultValue;
2358
2241
  }
2359
- const paramOptions = v.precompiledOptions || createParamOptions(v, k);
2242
+ const paramOptions = v.precompiledOptions || createParamOptions(v, v.index ?? k);
2360
2243
  return validateParam(app, ctx, rawValue, paramOptions, v.compiledValidator, v.compiledTypeConverter);
2361
2244
  });
2362
2245
  return Promise.all(paramPromises);
@@ -2569,7 +2452,19 @@ var GraphQLRouter = class {
2569
2452
  rootValue: routeHandler,
2570
2453
  validationRules: validationRules.length > 0 ? validationRules : void 0,
2571
2454
  formatError: this.options.ext?.debug ? void 0 : (error) => {
2572
- 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;
2573
2468
  },
2574
2469
  context: /* @__PURE__ */ __name((req) => {
2575
2470
  return req.koattyContext;
@@ -3213,8 +3108,12 @@ var GrpcRouter = class {
3213
3108
  }
3214
3109
  koatty_logger.DefaultLogger.Debug(`[GRPC_ROUTER] \u2705 Register request mapping: ["${path3}" => ${ctlItem.name}.${ctlItem.method}]`);
3215
3110
  impl[handler.name] = (call, callback) => {
3216
- koatty_logger.DefaultLogger.Warn(`[GRPC_ROUTER] \u26A0\uFE0F Placeholder handler called for: ${handler.name} - this should not happen!`);
3217
- 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
+ });
3218
3117
  };
3219
3118
  }
3220
3119
  if (Object.keys(impl).length > 0) {
@@ -4011,17 +3910,28 @@ function Get(name, defaultValue) {
4011
3910
  }
4012
3911
  __name(Get, "Get");
4013
3912
  function Post(name, defaultValue) {
4014
- return injectParam(async (ctx, opt) => {
4015
- const data = await bodyParser(ctx, opt);
4016
- const params = data.body ?? data;
4017
- return name ? params[name] : params;
4018
- }, "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
+ );
4019
3929
  }
4020
3930
  __name(Post, "Post");
4021
3931
  function File(name, defaultValue) {
4022
3932
  return injectParam(async (ctx, opt) => {
4023
3933
  const body = await bodyParser(ctx, opt);
4024
- const params = body.file ?? {};
3934
+ const params = body[FILE_KEY] ?? {};
4025
3935
  return name ? params[name] : params;
4026
3936
  }, "File", ParamSourceType.FILE, name, defaultValue);
4027
3937
  }