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