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/.turbo/turbo-build.log +201 -45
- package/.turbo/turbo-clean.log +4 -0
- package/.turbo/turbo-test.log +77 -0
- package/CHANGELOG.md +35 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +145 -235
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +148 -238
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +9 -9
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -0
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,
|
|
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-
|
|
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/
|
|
@@ -759,6 +759,9 @@ function RegisterMiddleware(app, config) {
|
|
|
759
759
|
};
|
|
760
760
|
}
|
|
761
761
|
__name(RegisterMiddleware, "RegisterMiddleware");
|
|
762
|
+
|
|
763
|
+
// src/payload/interface.ts
|
|
764
|
+
var FILE_KEY = /* @__PURE__ */ Symbol.for("koatty.files");
|
|
762
765
|
function parseText(ctx, opts) {
|
|
763
766
|
return getRawBody(inflate(ctx.req), opts).catch((err) => {
|
|
764
767
|
DefaultLogger.Error(err);
|
|
@@ -770,8 +773,9 @@ async function parseJson(ctx, opts) {
|
|
|
770
773
|
const str = await parseText(ctx, opts);
|
|
771
774
|
if (!str) return {};
|
|
772
775
|
try {
|
|
773
|
-
|
|
774
|
-
|
|
776
|
+
const parsed = JSON.parse(str);
|
|
777
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
|
|
778
|
+
value: parsed
|
|
775
779
|
};
|
|
776
780
|
} catch (error) {
|
|
777
781
|
DefaultLogger.Error(error);
|
|
@@ -780,18 +784,13 @@ async function parseJson(ctx, opts) {
|
|
|
780
784
|
}
|
|
781
785
|
__name(parseJson, "parseJson");
|
|
782
786
|
async function parseForm(ctx, opts) {
|
|
783
|
-
if (!ctx.request.headers["content-length"] || !ctx.request.headers["content-type"]?.includes("application/x-www-form-urlencoded")) {
|
|
784
|
-
return {};
|
|
785
|
-
}
|
|
786
787
|
const str = await parseText(ctx, opts);
|
|
787
788
|
if (!str || str.trim().length === 0) {
|
|
788
789
|
return {};
|
|
789
790
|
}
|
|
790
791
|
try {
|
|
791
792
|
const result = parse(str);
|
|
792
|
-
return
|
|
793
|
-
body: result
|
|
794
|
-
};
|
|
793
|
+
return result;
|
|
795
794
|
} catch (error) {
|
|
796
795
|
DefaultLogger.Error("[FormParseError]", error);
|
|
797
796
|
return {};
|
|
@@ -823,12 +822,6 @@ __name(deleteFiles, "deleteFiles");
|
|
|
823
822
|
|
|
824
823
|
// src/payload/parser/multipart.ts
|
|
825
824
|
function parseMultipart(ctx, opts) {
|
|
826
|
-
if (!ctx.request.headers["content-type"]?.includes("multipart/form-data")) {
|
|
827
|
-
return Promise.resolve({
|
|
828
|
-
body: {},
|
|
829
|
-
file: {}
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
825
|
const form = new IncomingForm({
|
|
833
826
|
encoding: opts.encoding,
|
|
834
827
|
multiples: opts.multiples,
|
|
@@ -851,15 +844,12 @@ function parseMultipart(ctx, opts) {
|
|
|
851
844
|
if (err) {
|
|
852
845
|
cleanup();
|
|
853
846
|
DefaultLogger.Error("[MultipartParseError]", err);
|
|
854
|
-
return resolve({
|
|
855
|
-
body: {},
|
|
856
|
-
file: {}
|
|
857
|
-
});
|
|
847
|
+
return resolve({});
|
|
858
848
|
}
|
|
859
849
|
uploadFiles = files;
|
|
860
850
|
resolve({
|
|
861
|
-
|
|
862
|
-
|
|
851
|
+
...fields,
|
|
852
|
+
[FILE_KEY]: files
|
|
863
853
|
});
|
|
864
854
|
});
|
|
865
855
|
});
|
|
@@ -873,8 +863,9 @@ async function parseXml(ctx, opts) {
|
|
|
873
863
|
const str = await parseText(ctx, opts);
|
|
874
864
|
if (!str) return {};
|
|
875
865
|
try {
|
|
876
|
-
|
|
877
|
-
|
|
866
|
+
const parsed = xmlParser.parse(str);
|
|
867
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
|
|
868
|
+
value: parsed
|
|
878
869
|
};
|
|
879
870
|
} catch (error) {
|
|
880
871
|
DefaultLogger.Error(error);
|
|
@@ -915,11 +906,14 @@ async function parseWebSocket(ctx, opts) {
|
|
|
915
906
|
const str = await parseText(ctx, opts);
|
|
916
907
|
if (!str) return {};
|
|
917
908
|
try {
|
|
918
|
-
|
|
919
|
-
|
|
909
|
+
const parsed = JSON.parse(str);
|
|
910
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {
|
|
911
|
+
value: parsed
|
|
920
912
|
};
|
|
921
913
|
} catch {
|
|
922
|
-
return
|
|
914
|
+
return {
|
|
915
|
+
value: str
|
|
916
|
+
};
|
|
923
917
|
}
|
|
924
918
|
} catch (error) {
|
|
925
919
|
DefaultLogger.Error("[WebSocketParseError]", error);
|
|
@@ -1139,6 +1133,7 @@ var PayloadCacheManager = class _PayloadCacheManager {
|
|
|
1139
1133
|
var cacheManager = PayloadCacheManager.getInstance();
|
|
1140
1134
|
|
|
1141
1135
|
// src/payload/payload.ts
|
|
1136
|
+
var bodyCache = /* @__PURE__ */ new WeakMap();
|
|
1142
1137
|
var supportedMethods = /* @__PURE__ */ new Set([
|
|
1143
1138
|
"POST",
|
|
1144
1139
|
"PUT",
|
|
@@ -1196,7 +1191,7 @@ function payload(options) {
|
|
|
1196
1191
|
if (!Object.prototype.hasOwnProperty.call(ctx, "requestFile")) {
|
|
1197
1192
|
Helper.define(ctx, "requestFile", async (name) => {
|
|
1198
1193
|
const body = await bodyParser(ctx, opts);
|
|
1199
|
-
const files = body
|
|
1194
|
+
const files = body[FILE_KEY] ?? {};
|
|
1200
1195
|
return name ? files[name] : files;
|
|
1201
1196
|
});
|
|
1202
1197
|
}
|
|
@@ -1216,22 +1211,32 @@ function queryParser(ctx, _options) {
|
|
|
1216
1211
|
return Object.assign({}, query, params);
|
|
1217
1212
|
}
|
|
1218
1213
|
__name(queryParser, "queryParser");
|
|
1219
|
-
|
|
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) {
|
|
1220
1221
|
try {
|
|
1221
|
-
|
|
1222
|
-
if (
|
|
1223
|
-
return body;
|
|
1224
|
-
}
|
|
1222
|
+
const cached = bodyCache.get(ctx);
|
|
1223
|
+
if (cached !== void 0) return cached;
|
|
1225
1224
|
const opts = cacheManager.getMergedOptions(options);
|
|
1226
|
-
body = await parseBody(ctx, opts);
|
|
1227
|
-
|
|
1225
|
+
const body = await parseBody(ctx, opts);
|
|
1226
|
+
bodyCache.set(ctx, body);
|
|
1227
|
+
try {
|
|
1228
|
+
ctx.setMetaData("_body", body);
|
|
1229
|
+
} catch {
|
|
1230
|
+
}
|
|
1228
1231
|
return body;
|
|
1229
1232
|
} catch (err) {
|
|
1230
1233
|
DefaultLogger.Error(err);
|
|
1231
|
-
|
|
1234
|
+
const empty = {};
|
|
1235
|
+
bodyCache.set(ctx, empty);
|
|
1236
|
+
return empty;
|
|
1232
1237
|
}
|
|
1233
1238
|
}
|
|
1234
|
-
__name(
|
|
1239
|
+
__name(parseBodyAndCache, "parseBodyAndCache");
|
|
1235
1240
|
function parseBody(ctx, options) {
|
|
1236
1241
|
if (!isSupportedMethod(ctx.method)) {
|
|
1237
1242
|
return Promise.resolve({});
|
|
@@ -1465,27 +1470,6 @@ function injectParamMetaData(app, target, options) {
|
|
|
1465
1470
|
if (!v.sourceType) {
|
|
1466
1471
|
v.sourceType = "custom";
|
|
1467
1472
|
}
|
|
1468
|
-
switch (v.sourceType) {
|
|
1469
|
-
case "query":
|
|
1470
|
-
v.extractorType = "query";
|
|
1471
|
-
break;
|
|
1472
|
-
case "body":
|
|
1473
|
-
v.extractorType = "body";
|
|
1474
|
-
break;
|
|
1475
|
-
case "header":
|
|
1476
|
-
v.extractorType = "header";
|
|
1477
|
-
break;
|
|
1478
|
-
case "path":
|
|
1479
|
-
v.extractorType = "path";
|
|
1480
|
-
break;
|
|
1481
|
-
case "file":
|
|
1482
|
-
v.extractorType = "file";
|
|
1483
|
-
break;
|
|
1484
|
-
case "custom":
|
|
1485
|
-
v.extractorType = "custom";
|
|
1486
|
-
break;
|
|
1487
|
-
}
|
|
1488
|
-
DefaultLogger.Debug(`Set extractorType ${v.extractorType} for param ${v.name}`);
|
|
1489
1473
|
const validEntry = validData.find((it) => v.index === it.index && it.name === v.name);
|
|
1490
1474
|
if (validEntry) {
|
|
1491
1475
|
v.validRule = validEntry.rule;
|
|
@@ -1559,15 +1543,6 @@ function injectParamMetaData(app, target, options) {
|
|
|
1559
1543
|
}
|
|
1560
1544
|
}
|
|
1561
1545
|
});
|
|
1562
|
-
const fastPathScenario = detectFastPathScenario(data);
|
|
1563
|
-
if (fastPathScenario) {
|
|
1564
|
-
const fastPathHandler = createFastPathHandler(fastPathScenario, data);
|
|
1565
|
-
if (fastPathHandler) {
|
|
1566
|
-
DefaultLogger.Debug(`Created fast path handler for ${target.constructor.name}.${meta}, scenario: ${fastPathScenario}`);
|
|
1567
|
-
data.fastPathHandler = fastPathHandler;
|
|
1568
|
-
data.fastPathScenario = fastPathScenario;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
1546
|
const hasAsyncParams = data.some(
|
|
1572
1547
|
(p) => p.sourceType === "body" || p.sourceType === "file" || p.isDto
|
|
1573
1548
|
// DTO validation might be async
|
|
@@ -1633,104 +1608,6 @@ function getControllerPath(className) {
|
|
|
1633
1608
|
return process.env.APP_PATH + "/controller/" + className + ".ts";
|
|
1634
1609
|
}
|
|
1635
1610
|
__name(getControllerPath, "getControllerPath");
|
|
1636
|
-
function detectFastPathScenario(params) {
|
|
1637
|
-
if (!params || params.length === 0) {
|
|
1638
|
-
return null;
|
|
1639
|
-
}
|
|
1640
|
-
if (params.length === 1) {
|
|
1641
|
-
const param = params[0];
|
|
1642
|
-
const fnName = param.fn?.name || "";
|
|
1643
|
-
if (fnName === "Get" && !param.validRule && !param.isDto) {
|
|
1644
|
-
DefaultLogger.Debug(`Detected fast path scenario A: single query param without validation`);
|
|
1645
|
-
return "SINGLE_QUERY_NO_VALIDATION";
|
|
1646
|
-
}
|
|
1647
|
-
if ((fnName === "Post" || fnName === "RequestBody") && param.isDto) {
|
|
1648
|
-
DefaultLogger.Debug(`Detected fast path scenario B: single DTO from body`);
|
|
1649
|
-
return "SINGLE_DTO_FROM_BODY";
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
if (params.length > 1) {
|
|
1653
|
-
const allQueryNoValidation = params.every((param) => {
|
|
1654
|
-
const fnName = param.fn?.name || "";
|
|
1655
|
-
return fnName === "Get" && !param.validRule && !param.isDto;
|
|
1656
|
-
});
|
|
1657
|
-
if (allQueryNoValidation) {
|
|
1658
|
-
DefaultLogger.Debug(`Detected fast path scenario C: multiple query params without validation`);
|
|
1659
|
-
return "MULTIPLE_QUERY_NO_VALIDATION";
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
return null;
|
|
1663
|
-
}
|
|
1664
|
-
__name(detectFastPathScenario, "detectFastPathScenario");
|
|
1665
|
-
function createFastPathHandler(scenario, params) {
|
|
1666
|
-
switch (scenario) {
|
|
1667
|
-
case "SINGLE_QUERY_NO_VALIDATION": {
|
|
1668
|
-
const param = params[0];
|
|
1669
|
-
const paramName = param.name;
|
|
1670
|
-
const paramType = param.type;
|
|
1671
|
-
if (paramName) {
|
|
1672
|
-
return (ctx) => {
|
|
1673
|
-
const value = ctx.query?.[paramName];
|
|
1674
|
-
const converted = convertParamsType(value, paramType);
|
|
1675
|
-
return [
|
|
1676
|
-
converted
|
|
1677
|
-
];
|
|
1678
|
-
};
|
|
1679
|
-
} else {
|
|
1680
|
-
return (ctx) => {
|
|
1681
|
-
const query = ctx.query || {};
|
|
1682
|
-
return [
|
|
1683
|
-
query
|
|
1684
|
-
];
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
case "SINGLE_DTO_FROM_BODY": {
|
|
1689
|
-
const param = params[0];
|
|
1690
|
-
const clazz = param.clazz;
|
|
1691
|
-
const dtoCheck = param.dtoCheck;
|
|
1692
|
-
if (!clazz) {
|
|
1693
|
-
DefaultLogger.Debug(`Cannot create fast path: DTO class not found`);
|
|
1694
|
-
return null;
|
|
1695
|
-
}
|
|
1696
|
-
return async (ctx) => {
|
|
1697
|
-
const body = await bodyParser(ctx, param.options);
|
|
1698
|
-
let validatedValue;
|
|
1699
|
-
if (dtoCheck) {
|
|
1700
|
-
validatedValue = await ClassValidator.valid(clazz, body, true);
|
|
1701
|
-
} else {
|
|
1702
|
-
validatedValue = plainToClass(clazz, body, true);
|
|
1703
|
-
}
|
|
1704
|
-
return [
|
|
1705
|
-
validatedValue
|
|
1706
|
-
];
|
|
1707
|
-
};
|
|
1708
|
-
}
|
|
1709
|
-
case "MULTIPLE_QUERY_NO_VALIDATION": {
|
|
1710
|
-
const paramConfigs = params.map((p) => ({
|
|
1711
|
-
name: p.name,
|
|
1712
|
-
type: p.type
|
|
1713
|
-
}));
|
|
1714
|
-
return (ctx) => {
|
|
1715
|
-
const query = ctx.query || {};
|
|
1716
|
-
const results = [];
|
|
1717
|
-
for (const config of paramConfigs) {
|
|
1718
|
-
if (config.name) {
|
|
1719
|
-
const value = query[config.name];
|
|
1720
|
-
const converted = convertParamsType(value, config.type);
|
|
1721
|
-
results.push(converted);
|
|
1722
|
-
} else {
|
|
1723
|
-
results.push(query);
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
return results;
|
|
1727
|
-
};
|
|
1728
|
-
}
|
|
1729
|
-
default:
|
|
1730
|
-
return null;
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
__name(createFastPathHandler, "createFastPathHandler");
|
|
1734
1611
|
function compileTypeConverter(type) {
|
|
1735
1612
|
const normalizedType = type.toLowerCase();
|
|
1736
1613
|
if (normalizedType === "string") {
|
|
@@ -1739,53 +1616,24 @@ function compileTypeConverter(type) {
|
|
|
1739
1616
|
switch (normalizedType) {
|
|
1740
1617
|
case "number":
|
|
1741
1618
|
return (value) => {
|
|
1619
|
+
if (typeof value === "number") return value;
|
|
1742
1620
|
if (value === null || value === void 0 || value === "") return value;
|
|
1743
|
-
|
|
1744
|
-
return isNaN(num) ? value : num;
|
|
1621
|
+
return convertParamsType(value, "number");
|
|
1745
1622
|
};
|
|
1746
1623
|
case "boolean":
|
|
1747
1624
|
return (value) => {
|
|
1748
|
-
if (value === null || value === void 0) return value;
|
|
1749
1625
|
if (typeof value === "boolean") return value;
|
|
1750
|
-
|
|
1751
|
-
const lower = value.toLowerCase();
|
|
1752
|
-
if (lower === "true" || lower === "1") return true;
|
|
1753
|
-
if (lower === "false" || lower === "0") return false;
|
|
1754
|
-
}
|
|
1755
|
-
return Boolean(value);
|
|
1626
|
+
return convertParamsType(value, "boolean");
|
|
1756
1627
|
};
|
|
1757
1628
|
case "array":
|
|
1758
1629
|
return (value) => {
|
|
1759
|
-
if (value === null || value === void 0) return value;
|
|
1760
1630
|
if (Array.isArray(value)) return value;
|
|
1761
|
-
|
|
1762
|
-
try {
|
|
1763
|
-
const parsed = JSON.parse(value);
|
|
1764
|
-
return Array.isArray(parsed) ? parsed : [
|
|
1765
|
-
value
|
|
1766
|
-
];
|
|
1767
|
-
} catch {
|
|
1768
|
-
return [
|
|
1769
|
-
value
|
|
1770
|
-
];
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
return [
|
|
1774
|
-
value
|
|
1775
|
-
];
|
|
1631
|
+
return convertParamsType(value, "array");
|
|
1776
1632
|
};
|
|
1777
1633
|
case "object":
|
|
1778
1634
|
return (value) => {
|
|
1779
|
-
if (value
|
|
1780
|
-
|
|
1781
|
-
if (typeof value === "string") {
|
|
1782
|
-
try {
|
|
1783
|
-
return JSON.parse(value);
|
|
1784
|
-
} catch {
|
|
1785
|
-
return value;
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
return value;
|
|
1635
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) return value;
|
|
1636
|
+
return convertParamsType(value, "object");
|
|
1789
1637
|
};
|
|
1790
1638
|
default:
|
|
1791
1639
|
return (value) => convertParamsType(value, type);
|
|
@@ -1813,7 +1661,22 @@ function generatePrecompiledExtractor(param) {
|
|
|
1813
1661
|
} else {
|
|
1814
1662
|
return (ctx) => ctx.params || {};
|
|
1815
1663
|
}
|
|
1816
|
-
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
|
+
}
|
|
1817
1680
|
case "file":
|
|
1818
1681
|
return null;
|
|
1819
1682
|
case "custom":
|
|
@@ -1876,14 +1739,19 @@ function extractValueFromSource(source, param) {
|
|
|
1876
1739
|
switch (param.sourceType) {
|
|
1877
1740
|
case ParamSourceType.QUERY:
|
|
1878
1741
|
return paramName ? source.query?.[paramName] : source.query;
|
|
1879
|
-
case ParamSourceType.BODY:
|
|
1880
|
-
|
|
1742
|
+
case ParamSourceType.BODY: {
|
|
1743
|
+
if (paramName) return source.body?.[paramName];
|
|
1744
|
+
const { [FILE_KEY]: _files, ...bodyFields } = source.body || {};
|
|
1745
|
+
return bodyFields;
|
|
1746
|
+
}
|
|
1881
1747
|
case ParamSourceType.HEADER:
|
|
1882
1748
|
return paramName ? source.headers?.[paramName] : source.headers;
|
|
1883
1749
|
case ParamSourceType.PATH:
|
|
1884
1750
|
return paramName ? source.params?.[paramName] : source.params;
|
|
1885
|
-
case ParamSourceType.FILE:
|
|
1886
|
-
|
|
1751
|
+
case ParamSourceType.FILE: {
|
|
1752
|
+
const files = source.body?.[FILE_KEY] || {};
|
|
1753
|
+
return paramName ? files[paramName] : files;
|
|
1754
|
+
}
|
|
1887
1755
|
case ParamSourceType.CUSTOM:
|
|
1888
1756
|
return null;
|
|
1889
1757
|
default:
|
|
@@ -1895,21 +1763,18 @@ async function extractParamSources(ctx, params) {
|
|
|
1895
1763
|
const needsBody = params.some((param) => {
|
|
1896
1764
|
return param.sourceType === ParamSourceType.BODY || param.sourceType === ParamSourceType.FILE;
|
|
1897
1765
|
});
|
|
1898
|
-
|
|
1766
|
+
let body = {};
|
|
1899
1767
|
if (needsBody) {
|
|
1900
1768
|
try {
|
|
1901
1769
|
const parsedBody = await bodyParser(ctx, params[0]?.options);
|
|
1902
|
-
|
|
1903
|
-
if (typeof parsedBody === "object" && "file" in parsedBody) {
|
|
1904
|
-
bodyData.file = parsedBody.file;
|
|
1905
|
-
}
|
|
1770
|
+
body = parsedBody || {};
|
|
1906
1771
|
} catch (err) {
|
|
1907
1772
|
DefaultLogger.Error(`extractParamSources: Failed to parse body: ${err.message}`);
|
|
1908
1773
|
}
|
|
1909
1774
|
}
|
|
1910
1775
|
return {
|
|
1911
1776
|
query: ctx.query || {},
|
|
1912
|
-
body
|
|
1777
|
+
body,
|
|
1913
1778
|
params: ctx.params || {},
|
|
1914
1779
|
headers: ctx.headers || {}
|
|
1915
1780
|
};
|
|
@@ -1939,7 +1804,7 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
|
|
|
1939
1804
|
}
|
|
1940
1805
|
return validatedValue;
|
|
1941
1806
|
} else {
|
|
1942
|
-
const needsConversion = compiledTypeConverter
|
|
1807
|
+
const needsConversion = compiledTypeConverter != null;
|
|
1943
1808
|
const needsValidation = !!(compiledValidator || opt.validRule);
|
|
1944
1809
|
if (!needsConversion && !needsValidation) {
|
|
1945
1810
|
return value;
|
|
@@ -1947,8 +1812,6 @@ async function validateParam(app, ctx, value, opt, compiledValidator, compiledTy
|
|
|
1947
1812
|
let convertedValue = value;
|
|
1948
1813
|
if (compiledTypeConverter) {
|
|
1949
1814
|
convertedValue = compiledTypeConverter(value);
|
|
1950
|
-
} else if (opt.type && opt.type !== "string") {
|
|
1951
|
-
convertedValue = convertParamsType(value, opt.type);
|
|
1952
1815
|
}
|
|
1953
1816
|
if (compiledValidator) {
|
|
1954
1817
|
compiledValidator(convertedValue);
|
|
@@ -2253,17 +2116,27 @@ var StrategyHandlerFactory = class {
|
|
|
2253
2116
|
return converted;
|
|
2254
2117
|
};
|
|
2255
2118
|
});
|
|
2119
|
+
const allAsyncHaveExtractor = asyncParams.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
|
|
2256
2120
|
return async (ctx) => {
|
|
2257
|
-
const bodyData = await extractParamSources(ctx, params);
|
|
2258
|
-
const asyncResults = asyncParams.map((p) => {
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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;
|
|
2263
2136
|
if (rawValue === void 0 && p.defaultValue !== void 0) {
|
|
2264
2137
|
return p.defaultValue;
|
|
2265
2138
|
}
|
|
2266
|
-
const paramOptions = p.precompiledOptions || createParamOptions(p, 0);
|
|
2139
|
+
const paramOptions = p.precompiledOptions || createParamOptions(p, p.index ?? 0);
|
|
2267
2140
|
return validateParam(app, ctx, rawValue, paramOptions, p.compiledValidator, p.compiledTypeConverter);
|
|
2268
2141
|
});
|
|
2269
2142
|
const syncResults = syncHandlers.map((h) => h(ctx));
|
|
@@ -2316,17 +2189,27 @@ var StrategyHandlerFactory = class {
|
|
|
2316
2189
|
* Fallback: Generic async path
|
|
2317
2190
|
*/
|
|
2318
2191
|
static createGenericAsyncHandler(params, app) {
|
|
2192
|
+
const allHaveExtractorOrFn = params.every((p) => !!p.precompiledExtractor || p.fn && typeof p.fn === "function");
|
|
2319
2193
|
return async (ctx) => {
|
|
2320
|
-
const sources = await extractParamSources(ctx, params);
|
|
2321
|
-
const paramPromises = params.map((v, k) => {
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
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;
|
|
2326
2209
|
if (rawValue === void 0 && v.defaultValue !== void 0) {
|
|
2327
|
-
|
|
2210
|
+
return v.defaultValue;
|
|
2328
2211
|
}
|
|
2329
|
-
const paramOptions = v.precompiledOptions || createParamOptions(v, k);
|
|
2212
|
+
const paramOptions = v.precompiledOptions || createParamOptions(v, v.index ?? k);
|
|
2330
2213
|
return validateParam(app, ctx, rawValue, paramOptions, v.compiledValidator, v.compiledTypeConverter);
|
|
2331
2214
|
});
|
|
2332
2215
|
return Promise.all(paramPromises);
|
|
@@ -2539,7 +2422,19 @@ var GraphQLRouter = class {
|
|
|
2539
2422
|
rootValue: routeHandler,
|
|
2540
2423
|
validationRules: validationRules.length > 0 ? validationRules : void 0,
|
|
2541
2424
|
formatError: this.options.ext?.debug ? void 0 : (error) => {
|
|
2542
|
-
|
|
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;
|
|
2543
2438
|
},
|
|
2544
2439
|
context: /* @__PURE__ */ __name((req) => {
|
|
2545
2440
|
return req.koattyContext;
|
|
@@ -3183,8 +3078,12 @@ var GrpcRouter = class {
|
|
|
3183
3078
|
}
|
|
3184
3079
|
DefaultLogger.Debug(`[GRPC_ROUTER] \u2705 Register request mapping: ["${path3}" => ${ctlItem.name}.${ctlItem.method}]`);
|
|
3185
3080
|
impl[handler.name] = (call, callback) => {
|
|
3186
|
-
|
|
3187
|
-
|
|
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
|
+
});
|
|
3188
3087
|
};
|
|
3189
3088
|
}
|
|
3190
3089
|
if (Object.keys(impl).length > 0) {
|
|
@@ -3981,17 +3880,28 @@ function Get(name, defaultValue) {
|
|
|
3981
3880
|
}
|
|
3982
3881
|
__name(Get, "Get");
|
|
3983
3882
|
function Post(name, defaultValue) {
|
|
3984
|
-
return injectParam(
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
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
|
+
);
|
|
3989
3899
|
}
|
|
3990
3900
|
__name(Post, "Post");
|
|
3991
3901
|
function File(name, defaultValue) {
|
|
3992
3902
|
return injectParam(async (ctx, opt) => {
|
|
3993
3903
|
const body = await bodyParser(ctx, opt);
|
|
3994
|
-
const params = body
|
|
3904
|
+
const params = body[FILE_KEY] ?? {};
|
|
3995
3905
|
return name ? params[name] : params;
|
|
3996
3906
|
}, "File", ParamSourceType.FILE, name, defaultValue);
|
|
3997
3907
|
}
|