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