@dangao/bun-server 1.1.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controller/decorators.d.ts +46 -1
- package/dist/controller/decorators.d.ts.map +1 -1
- package/dist/controller/index.d.ts +2 -2
- package/dist/controller/index.d.ts.map +1 -1
- package/dist/controller/param-binder.d.ts +12 -0
- package/dist/controller/param-binder.d.ts.map +1 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/context-service.d.ts +83 -0
- package/dist/core/context-service.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/di/container.d.ts +6 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +312 -125
- package/package.json +1 -1
- package/src/controller/decorators.ts +74 -0
- package/src/controller/index.ts +16 -2
- package/src/controller/param-binder.ts +91 -1
- package/src/core/application.ts +26 -19
- package/src/core/context-service.ts +134 -0
- package/src/core/index.ts +1 -0
- package/src/di/container.ts +81 -16
- package/src/index.ts +19 -2
- package/tests/controller/context-decorator.test.ts +183 -0
- package/tests/controller/param-map.test.ts +237 -0
- package/tests/core/context-service.test.ts +191 -0
- package/tests/di/scoped-lifecycle.test.ts +223 -0
- package/tests/middleware/rate-limit.test.ts +7 -2
- package/tests/utils/test-port.ts +21 -2
- /package/{readme.md → README.md} +0 -0
package/dist/index.js
CHANGED
|
@@ -774,6 +774,164 @@ class BunServer {
|
|
|
774
774
|
}
|
|
775
775
|
}
|
|
776
776
|
|
|
777
|
+
// src/core/context-service.ts
|
|
778
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
779
|
+
|
|
780
|
+
// src/di/decorators.ts
|
|
781
|
+
import"reflect-metadata";
|
|
782
|
+
import { LoggerManager as LoggerManager2 } from "@dangao/logsmith";
|
|
783
|
+
var DEPENDENCY_METADATA_KEY = Symbol("dependency:metadata");
|
|
784
|
+
var INJECTABLE_METADATA_KEY = Symbol("injectable");
|
|
785
|
+
var typeReferenceMap = new WeakMap;
|
|
786
|
+
function Injectable(config) {
|
|
787
|
+
return function(target) {
|
|
788
|
+
Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
|
|
789
|
+
if (config?.lifecycle) {
|
|
790
|
+
Reflect.defineMetadata("lifecycle", config.lifecycle, target);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function Inject(token) {
|
|
795
|
+
return function(target, _propertyKey, parameterIndex) {
|
|
796
|
+
const logger = LoggerManager2.getLogger();
|
|
797
|
+
const constructor = typeof target === "function" ? target : target?.constructor;
|
|
798
|
+
if (!constructor) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", constructor);
|
|
802
|
+
const paramType = paramTypes?.[parameterIndex];
|
|
803
|
+
let metadata = Reflect.getMetadata(DEPENDENCY_METADATA_KEY, constructor) || [];
|
|
804
|
+
const paramCount = paramTypes?.length || 0;
|
|
805
|
+
while (metadata.length < paramCount) {
|
|
806
|
+
metadata.push(undefined);
|
|
807
|
+
}
|
|
808
|
+
let dependencyType;
|
|
809
|
+
let dependencyToken;
|
|
810
|
+
if (token) {
|
|
811
|
+
if (typeof token === "string" || typeof token === "symbol") {
|
|
812
|
+
dependencyToken = token;
|
|
813
|
+
dependencyType = paramType || Object;
|
|
814
|
+
} else {
|
|
815
|
+
dependencyType = token;
|
|
816
|
+
}
|
|
817
|
+
} else {
|
|
818
|
+
if (!paramType) {
|
|
819
|
+
throw new Error(`Cannot determine dependency type for parameter ${parameterIndex} of ${constructor.name}. ` + "Please provide explicit type using @Inject(Type) or ensure emitDecoratorMetadata is enabled.");
|
|
820
|
+
}
|
|
821
|
+
dependencyType = paramType;
|
|
822
|
+
}
|
|
823
|
+
if (!typeReferenceMap.has(constructor)) {
|
|
824
|
+
typeReferenceMap.set(constructor, new Map);
|
|
825
|
+
}
|
|
826
|
+
const typeRefs = typeReferenceMap.get(constructor);
|
|
827
|
+
typeRefs.set(String(parameterIndex), dependencyType);
|
|
828
|
+
metadata[parameterIndex] = {
|
|
829
|
+
index: parameterIndex,
|
|
830
|
+
type: dependencyType,
|
|
831
|
+
token: dependencyToken
|
|
832
|
+
};
|
|
833
|
+
if (constructor.name === "Service" || constructor.name === "Level2") {
|
|
834
|
+
logger.debug(`[DI Debug] @Inject(${token ? typeof token === "function" ? token.name : String(token) : "auto"}) on ${constructor.name}[${parameterIndex}]: saving metadata.length=${metadata.length}`);
|
|
835
|
+
}
|
|
836
|
+
Reflect.defineMetadata(DEPENDENCY_METADATA_KEY, metadata, constructor);
|
|
837
|
+
if (constructor.name === "Service" || constructor.name === "Level2") {
|
|
838
|
+
const savedMetadata = Reflect.getMetadata(DEPENDENCY_METADATA_KEY, constructor);
|
|
839
|
+
logger.debug(`[DI Debug] @Inject on ${constructor.name}: saved metadata.exists=${savedMetadata ? "yes" : "no"}, length=${savedMetadata?.length || 0}`);
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function getDependencyMetadata(target) {
|
|
844
|
+
const constructor = typeof target === "function" ? target : target?.constructor;
|
|
845
|
+
if (!constructor) {
|
|
846
|
+
return [];
|
|
847
|
+
}
|
|
848
|
+
const rawMetadata = Reflect.getMetadata(DEPENDENCY_METADATA_KEY, constructor);
|
|
849
|
+
const metadata = rawMetadata || [];
|
|
850
|
+
if (constructor.name === "Service" || constructor.name === "Level2") {
|
|
851
|
+
LoggerManager2.getLogger().debug(`[DI Debug] getDependencyMetadata(${constructor.name}): rawMetadata=${rawMetadata ? "exists" : "undefined"}, length=${metadata.length}`);
|
|
852
|
+
}
|
|
853
|
+
const typeRefs = typeReferenceMap.get(constructor);
|
|
854
|
+
if (typeRefs) {
|
|
855
|
+
for (let i = 0;i < metadata.length; i++) {
|
|
856
|
+
const meta = metadata[i];
|
|
857
|
+
if (meta !== undefined && meta !== null) {
|
|
858
|
+
const typeRef = typeRefs.get(String(meta.index));
|
|
859
|
+
if (typeRef && typeof typeRef === "function") {
|
|
860
|
+
meta.type = typeRef;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return metadata;
|
|
866
|
+
}
|
|
867
|
+
function getLifecycle(target) {
|
|
868
|
+
return Reflect.getMetadata("lifecycle", target);
|
|
869
|
+
}
|
|
870
|
+
function getTypeReference(constructor, parameterIndex) {
|
|
871
|
+
const typeRefs = typeReferenceMap.get(constructor);
|
|
872
|
+
if (typeRefs) {
|
|
873
|
+
return typeRefs.get(String(parameterIndex));
|
|
874
|
+
}
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/core/context-service.ts
|
|
879
|
+
var contextStore = new AsyncLocalStorage;
|
|
880
|
+
var CONTEXT_SERVICE_TOKEN = Symbol("ContextService");
|
|
881
|
+
|
|
882
|
+
class ContextService {
|
|
883
|
+
getContext() {
|
|
884
|
+
return contextStore.getStore();
|
|
885
|
+
}
|
|
886
|
+
getHeader(key) {
|
|
887
|
+
const context = this.getContext();
|
|
888
|
+
return context?.getHeader(key) ?? null;
|
|
889
|
+
}
|
|
890
|
+
getQuery(key) {
|
|
891
|
+
const context = this.getContext();
|
|
892
|
+
return context?.getQuery(key) ?? null;
|
|
893
|
+
}
|
|
894
|
+
getQueryAll() {
|
|
895
|
+
const context = this.getContext();
|
|
896
|
+
return context?.getQueryAll() ?? {};
|
|
897
|
+
}
|
|
898
|
+
getParam(key) {
|
|
899
|
+
const context = this.getContext();
|
|
900
|
+
return context?.getParam(key);
|
|
901
|
+
}
|
|
902
|
+
getBody() {
|
|
903
|
+
const context = this.getContext();
|
|
904
|
+
return context?.body ?? null;
|
|
905
|
+
}
|
|
906
|
+
getMethod() {
|
|
907
|
+
const context = this.getContext();
|
|
908
|
+
return context?.method ?? "";
|
|
909
|
+
}
|
|
910
|
+
getPath() {
|
|
911
|
+
const context = this.getContext();
|
|
912
|
+
return context?.path ?? "";
|
|
913
|
+
}
|
|
914
|
+
getUrl() {
|
|
915
|
+
const context = this.getContext();
|
|
916
|
+
return context?.url;
|
|
917
|
+
}
|
|
918
|
+
getClientIp() {
|
|
919
|
+
const context = this.getContext();
|
|
920
|
+
return context?.getClientIp() ?? "unknown";
|
|
921
|
+
}
|
|
922
|
+
setHeader(key, value) {
|
|
923
|
+
const context = this.getContext();
|
|
924
|
+
context?.setHeader(key, value);
|
|
925
|
+
}
|
|
926
|
+
setStatus(code) {
|
|
927
|
+
const context = this.getContext();
|
|
928
|
+
context?.setStatus(code);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
ContextService = __legacyDecorateClassTS([
|
|
932
|
+
Injectable()
|
|
933
|
+
], ContextService);
|
|
934
|
+
|
|
777
935
|
// src/middleware/pipeline.ts
|
|
778
936
|
class MiddlewarePipeline {
|
|
779
937
|
middlewares = [];
|
|
@@ -1043,107 +1201,8 @@ var Lifecycle;
|
|
|
1043
1201
|
Lifecycle2["Scoped"] = "scoped";
|
|
1044
1202
|
})(Lifecycle ||= {});
|
|
1045
1203
|
|
|
1046
|
-
// src/di/decorators.ts
|
|
1047
|
-
import"reflect-metadata";
|
|
1048
|
-
import { LoggerManager as LoggerManager2 } from "@dangao/logsmith";
|
|
1049
|
-
var DEPENDENCY_METADATA_KEY = Symbol("dependency:metadata");
|
|
1050
|
-
var INJECTABLE_METADATA_KEY = Symbol("injectable");
|
|
1051
|
-
var typeReferenceMap = new WeakMap;
|
|
1052
|
-
function Injectable(config) {
|
|
1053
|
-
return function(target) {
|
|
1054
|
-
Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);
|
|
1055
|
-
if (config?.lifecycle) {
|
|
1056
|
-
Reflect.defineMetadata("lifecycle", config.lifecycle, target);
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
function Inject(token) {
|
|
1061
|
-
return function(target, _propertyKey, parameterIndex) {
|
|
1062
|
-
const logger = LoggerManager2.getLogger();
|
|
1063
|
-
const constructor = typeof target === "function" ? target : target?.constructor;
|
|
1064
|
-
if (!constructor) {
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
const paramTypes = Reflect.getMetadata("design:paramtypes", constructor);
|
|
1068
|
-
const paramType = paramTypes?.[parameterIndex];
|
|
1069
|
-
let metadata = Reflect.getMetadata(DEPENDENCY_METADATA_KEY, constructor) || [];
|
|
1070
|
-
const paramCount = paramTypes?.length || 0;
|
|
1071
|
-
while (metadata.length < paramCount) {
|
|
1072
|
-
metadata.push(undefined);
|
|
1073
|
-
}
|
|
1074
|
-
let dependencyType;
|
|
1075
|
-
let dependencyToken;
|
|
1076
|
-
if (token) {
|
|
1077
|
-
if (typeof token === "string" || typeof token === "symbol") {
|
|
1078
|
-
dependencyToken = token;
|
|
1079
|
-
dependencyType = paramType || Object;
|
|
1080
|
-
} else {
|
|
1081
|
-
dependencyType = token;
|
|
1082
|
-
}
|
|
1083
|
-
} else {
|
|
1084
|
-
if (!paramType) {
|
|
1085
|
-
throw new Error(`Cannot determine dependency type for parameter ${parameterIndex} of ${constructor.name}. ` + "Please provide explicit type using @Inject(Type) or ensure emitDecoratorMetadata is enabled.");
|
|
1086
|
-
}
|
|
1087
|
-
dependencyType = paramType;
|
|
1088
|
-
}
|
|
1089
|
-
if (!typeReferenceMap.has(constructor)) {
|
|
1090
|
-
typeReferenceMap.set(constructor, new Map);
|
|
1091
|
-
}
|
|
1092
|
-
const typeRefs = typeReferenceMap.get(constructor);
|
|
1093
|
-
typeRefs.set(String(parameterIndex), dependencyType);
|
|
1094
|
-
metadata[parameterIndex] = {
|
|
1095
|
-
index: parameterIndex,
|
|
1096
|
-
type: dependencyType,
|
|
1097
|
-
token: dependencyToken
|
|
1098
|
-
};
|
|
1099
|
-
if (constructor.name === "Service" || constructor.name === "Level2") {
|
|
1100
|
-
logger.debug(`[DI Debug] @Inject(${token ? typeof token === "function" ? token.name : String(token) : "auto"}) on ${constructor.name}[${parameterIndex}]: saving metadata.length=${metadata.length}`);
|
|
1101
|
-
}
|
|
1102
|
-
Reflect.defineMetadata(DEPENDENCY_METADATA_KEY, metadata, constructor);
|
|
1103
|
-
if (constructor.name === "Service" || constructor.name === "Level2") {
|
|
1104
|
-
const savedMetadata = Reflect.getMetadata(DEPENDENCY_METADATA_KEY, constructor);
|
|
1105
|
-
logger.debug(`[DI Debug] @Inject on ${constructor.name}: saved metadata.exists=${savedMetadata ? "yes" : "no"}, length=${savedMetadata?.length || 0}`);
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
function getDependencyMetadata(target) {
|
|
1110
|
-
const constructor = typeof target === "function" ? target : target?.constructor;
|
|
1111
|
-
if (!constructor) {
|
|
1112
|
-
return [];
|
|
1113
|
-
}
|
|
1114
|
-
const rawMetadata = Reflect.getMetadata(DEPENDENCY_METADATA_KEY, constructor);
|
|
1115
|
-
const metadata = rawMetadata || [];
|
|
1116
|
-
if (constructor.name === "Service" || constructor.name === "Level2") {
|
|
1117
|
-
LoggerManager2.getLogger().debug(`[DI Debug] getDependencyMetadata(${constructor.name}): rawMetadata=${rawMetadata ? "exists" : "undefined"}, length=${metadata.length}`);
|
|
1118
|
-
}
|
|
1119
|
-
const typeRefs = typeReferenceMap.get(constructor);
|
|
1120
|
-
if (typeRefs) {
|
|
1121
|
-
for (let i = 0;i < metadata.length; i++) {
|
|
1122
|
-
const meta = metadata[i];
|
|
1123
|
-
if (meta !== undefined && meta !== null) {
|
|
1124
|
-
const typeRef = typeRefs.get(String(meta.index));
|
|
1125
|
-
if (typeRef && typeof typeRef === "function") {
|
|
1126
|
-
meta.type = typeRef;
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
return metadata;
|
|
1132
|
-
}
|
|
1133
|
-
function getLifecycle(target) {
|
|
1134
|
-
return Reflect.getMetadata("lifecycle", target);
|
|
1135
|
-
}
|
|
1136
|
-
function getTypeReference(constructor, parameterIndex) {
|
|
1137
|
-
const typeRefs = typeReferenceMap.get(constructor);
|
|
1138
|
-
if (typeRefs) {
|
|
1139
|
-
return typeRefs.get(String(parameterIndex));
|
|
1140
|
-
}
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
1204
|
// src/di/container.ts
|
|
1145
1205
|
import { LoggerManager as LoggerManager3 } from "@dangao/logsmith";
|
|
1146
|
-
|
|
1147
1206
|
class Container {
|
|
1148
1207
|
parent;
|
|
1149
1208
|
constructor(options = {}) {
|
|
@@ -1151,6 +1210,7 @@ class Container {
|
|
|
1151
1210
|
}
|
|
1152
1211
|
providers = new Map;
|
|
1153
1212
|
singletons = new Map;
|
|
1213
|
+
scopedInstances = new WeakMap;
|
|
1154
1214
|
typeToToken = new Map;
|
|
1155
1215
|
dependencyPlans = new Map;
|
|
1156
1216
|
register(token, config) {
|
|
@@ -1200,6 +1260,20 @@ class Container {
|
|
|
1200
1260
|
return singleton;
|
|
1201
1261
|
}
|
|
1202
1262
|
}
|
|
1263
|
+
if (provider.lifecycle === "scoped" /* Scoped */) {
|
|
1264
|
+
const context = contextStore.getStore();
|
|
1265
|
+
if (context) {
|
|
1266
|
+
let scopedMap = this.scopedInstances.get(context);
|
|
1267
|
+
if (!scopedMap) {
|
|
1268
|
+
scopedMap = new Map;
|
|
1269
|
+
this.scopedInstances.set(context, scopedMap);
|
|
1270
|
+
}
|
|
1271
|
+
const scopedInstance = scopedMap.get(tokenKey);
|
|
1272
|
+
if (scopedInstance) {
|
|
1273
|
+
return scopedInstance;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1203
1277
|
let instance;
|
|
1204
1278
|
if (provider.factory) {
|
|
1205
1279
|
instance = provider.factory();
|
|
@@ -1213,6 +1287,17 @@ class Container {
|
|
|
1213
1287
|
if (provider.lifecycle === "singleton" /* Singleton */) {
|
|
1214
1288
|
this.singletons.set(tokenKey, instance);
|
|
1215
1289
|
}
|
|
1290
|
+
if (provider.lifecycle === "scoped" /* Scoped */) {
|
|
1291
|
+
const context = contextStore.getStore();
|
|
1292
|
+
if (context) {
|
|
1293
|
+
let scopedMap = this.scopedInstances.get(context);
|
|
1294
|
+
if (!scopedMap) {
|
|
1295
|
+
scopedMap = new Map;
|
|
1296
|
+
this.scopedInstances.set(context, scopedMap);
|
|
1297
|
+
}
|
|
1298
|
+
scopedMap.set(tokenKey, instance);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1216
1301
|
return instance;
|
|
1217
1302
|
}
|
|
1218
1303
|
resolveInternal(token) {
|
|
@@ -1236,21 +1321,43 @@ class Container {
|
|
|
1236
1321
|
return singleton;
|
|
1237
1322
|
}
|
|
1238
1323
|
}
|
|
1239
|
-
if (provider.
|
|
1240
|
-
const
|
|
1241
|
-
if (
|
|
1242
|
-
this.
|
|
1324
|
+
if (provider.lifecycle === "scoped" /* Scoped */) {
|
|
1325
|
+
const context = contextStore.getStore();
|
|
1326
|
+
if (context) {
|
|
1327
|
+
let scopedMap = this.scopedInstances.get(context);
|
|
1328
|
+
if (!scopedMap) {
|
|
1329
|
+
scopedMap = new Map;
|
|
1330
|
+
this.scopedInstances.set(context, scopedMap);
|
|
1331
|
+
}
|
|
1332
|
+
const scopedInstance = scopedMap.get(token);
|
|
1333
|
+
if (scopedInstance) {
|
|
1334
|
+
return scopedInstance;
|
|
1335
|
+
}
|
|
1243
1336
|
}
|
|
1244
|
-
return instance;
|
|
1245
1337
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1338
|
+
let instance;
|
|
1339
|
+
if (provider.factory) {
|
|
1340
|
+
instance = provider.factory();
|
|
1341
|
+
} else if (provider.implementation && typeof provider.implementation === "function") {
|
|
1342
|
+
instance = this.instantiate(provider.implementation);
|
|
1343
|
+
} else {
|
|
1344
|
+
throw new Error(`Cannot instantiate token: ${String(token)}. Factory function required.`);
|
|
1345
|
+
}
|
|
1346
|
+
if (provider.lifecycle === "singleton" /* Singleton */) {
|
|
1347
|
+
this.singletons.set(token, instance);
|
|
1348
|
+
}
|
|
1349
|
+
if (provider.lifecycle === "scoped" /* Scoped */) {
|
|
1350
|
+
const context = contextStore.getStore();
|
|
1351
|
+
if (context) {
|
|
1352
|
+
let scopedMap = this.scopedInstances.get(context);
|
|
1353
|
+
if (!scopedMap) {
|
|
1354
|
+
scopedMap = new Map;
|
|
1355
|
+
this.scopedInstances.set(context, scopedMap);
|
|
1356
|
+
}
|
|
1357
|
+
scopedMap.set(token, instance);
|
|
1250
1358
|
}
|
|
1251
|
-
return instance;
|
|
1252
1359
|
}
|
|
1253
|
-
|
|
1360
|
+
return instance;
|
|
1254
1361
|
}
|
|
1255
1362
|
if (typeof token === "function") {
|
|
1256
1363
|
return this.resolve(token);
|
|
@@ -1371,6 +1478,25 @@ function Param(key) {
|
|
|
1371
1478
|
function Header(key) {
|
|
1372
1479
|
return createParamDecorator("header" /* HEADER */, key);
|
|
1373
1480
|
}
|
|
1481
|
+
function QueryMap(options) {
|
|
1482
|
+
return function(target, propertyKey, parameterIndex) {
|
|
1483
|
+
const existingParams = Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
1484
|
+
const normalizedOptions = typeof options === "function" ? { transform: options } : options ?? {};
|
|
1485
|
+
existingParams.push({ type: "query_map" /* QUERY_MAP */, index: parameterIndex, options: normalizedOptions });
|
|
1486
|
+
Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey);
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
function HeaderMap(options) {
|
|
1490
|
+
return function(target, propertyKey, parameterIndex) {
|
|
1491
|
+
const existingParams = Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
1492
|
+
const normalizedOptions = typeof options === "function" ? { transform: options } : options ?? {};
|
|
1493
|
+
existingParams.push({ type: "header_map" /* HEADER_MAP */, index: parameterIndex, options: normalizedOptions });
|
|
1494
|
+
Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey);
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
function Context2() {
|
|
1498
|
+
return createParamDecorator("context" /* CONTEXT */);
|
|
1499
|
+
}
|
|
1374
1500
|
function getParamMetadata(target, propertyKey) {
|
|
1375
1501
|
return Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
1376
1502
|
}
|
|
@@ -1578,6 +1704,12 @@ class ParamBinder {
|
|
|
1578
1704
|
}
|
|
1579
1705
|
} catch {}
|
|
1580
1706
|
return;
|
|
1707
|
+
case "context" /* CONTEXT */:
|
|
1708
|
+
return contextStore.getStore() ?? context;
|
|
1709
|
+
case "query_map" /* QUERY_MAP */:
|
|
1710
|
+
return await this.getQueryMapValue(meta.options, context);
|
|
1711
|
+
case "header_map" /* HEADER_MAP */:
|
|
1712
|
+
return await this.getHeaderMapValue(meta.options, context);
|
|
1581
1713
|
default:
|
|
1582
1714
|
return;
|
|
1583
1715
|
}
|
|
@@ -1601,6 +1733,52 @@ class ParamBinder {
|
|
|
1601
1733
|
static getHeaderValue(key, context) {
|
|
1602
1734
|
return context.getHeader(key);
|
|
1603
1735
|
}
|
|
1736
|
+
static async getQueryMapValue(options, context) {
|
|
1737
|
+
const result = {};
|
|
1738
|
+
const searchParams = context.query;
|
|
1739
|
+
for (const key of searchParams.keys()) {
|
|
1740
|
+
const values = searchParams.getAll(key);
|
|
1741
|
+
if (values.length === 1) {
|
|
1742
|
+
result[key] = values[0];
|
|
1743
|
+
} else {
|
|
1744
|
+
result[key] = values;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
let output = result;
|
|
1748
|
+
if (options?.transform) {
|
|
1749
|
+
output = await options.transform(result);
|
|
1750
|
+
}
|
|
1751
|
+
if (options?.validate) {
|
|
1752
|
+
await options.validate(output);
|
|
1753
|
+
}
|
|
1754
|
+
return output;
|
|
1755
|
+
}
|
|
1756
|
+
static async getHeaderMapValue(options, context) {
|
|
1757
|
+
const normalize = options?.normalize ?? true;
|
|
1758
|
+
const pick = options?.pick?.map((key) => key.toLowerCase());
|
|
1759
|
+
const headers = context.headers;
|
|
1760
|
+
const result = {};
|
|
1761
|
+
headers.forEach((value, rawKey) => {
|
|
1762
|
+
const key = normalize ? rawKey.toLowerCase() : rawKey;
|
|
1763
|
+
if (pick && !pick.includes(key)) {
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const parts = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1767
|
+
if (parts.length <= 1) {
|
|
1768
|
+
result[key] = parts[0] ?? "";
|
|
1769
|
+
} else {
|
|
1770
|
+
result[key] = parts;
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
let output = result;
|
|
1774
|
+
if (options?.transform) {
|
|
1775
|
+
output = await options.transform(result);
|
|
1776
|
+
}
|
|
1777
|
+
if (options?.validate) {
|
|
1778
|
+
await options.validate(output);
|
|
1779
|
+
}
|
|
1780
|
+
return output;
|
|
1781
|
+
}
|
|
1604
1782
|
}
|
|
1605
1783
|
|
|
1606
1784
|
// src/controller/metadata.ts
|
|
@@ -2903,6 +3081,7 @@ class Application {
|
|
|
2903
3081
|
const container = ControllerRegistry.getInstance().getContainer();
|
|
2904
3082
|
const interceptorRegistry = new InterceptorRegistry;
|
|
2905
3083
|
container.registerInstance(INTERCEPTOR_REGISTRY_TOKEN, interceptorRegistry);
|
|
3084
|
+
container.registerInstance(CONTEXT_SERVICE_TOKEN, new ContextService);
|
|
2906
3085
|
this.registerExtension(new LoggerExtension);
|
|
2907
3086
|
}
|
|
2908
3087
|
use(middleware) {
|
|
@@ -2944,19 +3123,21 @@ class Application {
|
|
|
2944
3123
|
}
|
|
2945
3124
|
}
|
|
2946
3125
|
async handleRequest(context) {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3126
|
+
return await contextStore.run(context, async () => {
|
|
3127
|
+
if (["POST", "PUT", "PATCH"].includes(context.method)) {
|
|
3128
|
+
await context.getBody();
|
|
3129
|
+
}
|
|
3130
|
+
const registry = RouteRegistry.getInstance();
|
|
3131
|
+
const router = registry.getRouter();
|
|
3132
|
+
await router.preHandle(context);
|
|
3133
|
+
return await this.middlewarePipeline.run(context, async () => {
|
|
3134
|
+
const response = await router.handle(context);
|
|
3135
|
+
if (response) {
|
|
3136
|
+
return response;
|
|
3137
|
+
}
|
|
3138
|
+
context.setStatus(404);
|
|
3139
|
+
return context.createResponse({ error: "Not Found" });
|
|
3140
|
+
});
|
|
2960
3141
|
});
|
|
2961
3142
|
}
|
|
2962
3143
|
registerController(controllerClass) {
|
|
@@ -3445,7 +3626,7 @@ SwaggerModule = __legacyDecorateClassTS([
|
|
|
3445
3626
|
})
|
|
3446
3627
|
], SwaggerModule);
|
|
3447
3628
|
// src/security/context.ts
|
|
3448
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
3629
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
3449
3630
|
|
|
3450
3631
|
class SecurityContextImpl {
|
|
3451
3632
|
_authentication = null;
|
|
@@ -3470,7 +3651,7 @@ class SecurityContextImpl {
|
|
|
3470
3651
|
}
|
|
3471
3652
|
|
|
3472
3653
|
class SecurityContextHolder {
|
|
3473
|
-
static storage = new
|
|
3654
|
+
static storage = new AsyncLocalStorage2;
|
|
3474
3655
|
static getContext() {
|
|
3475
3656
|
let context = this.storage.getStore();
|
|
3476
3657
|
if (!context) {
|
|
@@ -6630,6 +6811,7 @@ export {
|
|
|
6630
6811
|
createFileUploadMiddleware,
|
|
6631
6812
|
createErrorHandlingMiddleware,
|
|
6632
6813
|
createCorsMiddleware,
|
|
6814
|
+
contextStore,
|
|
6633
6815
|
checkRoles,
|
|
6634
6816
|
WebSocketGatewayRegistry,
|
|
6635
6817
|
WebSocketGateway,
|
|
@@ -6666,6 +6848,7 @@ export {
|
|
|
6666
6848
|
QueueService,
|
|
6667
6849
|
QueueModule,
|
|
6668
6850
|
Queue,
|
|
6851
|
+
QueryMap,
|
|
6669
6852
|
Query,
|
|
6670
6853
|
QUEUE_SERVICE_TOKEN,
|
|
6671
6854
|
QUEUE_OPTIONS_TOKEN,
|
|
@@ -6726,6 +6909,7 @@ export {
|
|
|
6726
6909
|
INTERCEPTOR_REGISTRY_TOKEN,
|
|
6727
6910
|
HttpException,
|
|
6728
6911
|
HealthModule,
|
|
6912
|
+
HeaderMap,
|
|
6729
6913
|
Header,
|
|
6730
6914
|
HEALTH_OPTIONS_TOKEN,
|
|
6731
6915
|
HEALTH_INDICATORS_TOKEN,
|
|
@@ -6745,6 +6929,8 @@ export {
|
|
|
6745
6929
|
Cron,
|
|
6746
6930
|
ControllerRegistry,
|
|
6747
6931
|
Controller,
|
|
6932
|
+
ContextService,
|
|
6933
|
+
Context2 as ContextParam,
|
|
6748
6934
|
Context,
|
|
6749
6935
|
Container,
|
|
6750
6936
|
ConnectionPool,
|
|
@@ -6758,6 +6944,7 @@ export {
|
|
|
6758
6944
|
CacheInterceptor,
|
|
6759
6945
|
CacheEvict,
|
|
6760
6946
|
Cache,
|
|
6947
|
+
CONTEXT_SERVICE_TOKEN,
|
|
6761
6948
|
CONFIG_SERVICE_TOKEN,
|
|
6762
6949
|
CACHE_SERVICE_TOKEN,
|
|
6763
6950
|
CACHE_OPTIONS_TOKEN,
|
package/package.json
CHANGED
|
@@ -14,6 +14,9 @@ export enum ParamType {
|
|
|
14
14
|
PARAM = 'param',
|
|
15
15
|
HEADER = 'header',
|
|
16
16
|
SESSION = 'session',
|
|
17
|
+
CONTEXT = 'context',
|
|
18
|
+
QUERY_MAP = 'query_map',
|
|
19
|
+
HEADER_MAP = 'header_map',
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -23,6 +26,7 @@ export interface ParamMetadata {
|
|
|
23
26
|
type: ParamType;
|
|
24
27
|
key?: string;
|
|
25
28
|
index: number;
|
|
29
|
+
options?: unknown;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -72,6 +76,76 @@ export function Header(key: string) {
|
|
|
72
76
|
return createParamDecorator(ParamType.HEADER, key);
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
/**
|
|
80
|
+
* QueryMap 注解选项
|
|
81
|
+
*/
|
|
82
|
+
export interface QueryMapOptions<T = unknown> {
|
|
83
|
+
transform?: (input: Record<string, string | string[]>) => T | Promise<T>;
|
|
84
|
+
validate?: (dto: T) => void | Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* HeaderMap 注解选项
|
|
89
|
+
*/
|
|
90
|
+
export interface HeaderMapOptions<T = unknown> {
|
|
91
|
+
normalize?: boolean;
|
|
92
|
+
pick?: string[];
|
|
93
|
+
transform?: (input: Record<string, string | string[]>) => T | Promise<T>;
|
|
94
|
+
validate?: (dto: T) => void | Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* QueryMap 参数装饰器
|
|
99
|
+
* 一次性注入完整查询对象
|
|
100
|
+
*/
|
|
101
|
+
export function QueryMap<T = Record<string, string | string[]>>(
|
|
102
|
+
options?: QueryMapOptions<T> | ((input: Record<string, string | string[]>) => T | Promise<T>),
|
|
103
|
+
) {
|
|
104
|
+
return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
|
|
105
|
+
const existingParams: ParamMetadata[] =
|
|
106
|
+
Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey as string) || [];
|
|
107
|
+
const normalizedOptions: QueryMapOptions<T> =
|
|
108
|
+
typeof options === 'function' ? { transform: options } : options ?? {};
|
|
109
|
+
existingParams.push({ type: ParamType.QUERY_MAP, index: parameterIndex, options: normalizedOptions });
|
|
110
|
+
Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey as string);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* HeaderMap 参数装饰器
|
|
116
|
+
* 一次性注入完整 headers 对象
|
|
117
|
+
*/
|
|
118
|
+
export function HeaderMap<T = Record<string, string | string[]>>(
|
|
119
|
+
options?: HeaderMapOptions<T> | ((input: Record<string, string | string[]>) => T | Promise<T>),
|
|
120
|
+
) {
|
|
121
|
+
return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
|
|
122
|
+
const existingParams: ParamMetadata[] =
|
|
123
|
+
Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey as string) || [];
|
|
124
|
+
const normalizedOptions: HeaderMapOptions<T> =
|
|
125
|
+
typeof options === 'function' ? { transform: options } : options ?? {};
|
|
126
|
+
existingParams.push({ type: ParamType.HEADER_MAP, index: parameterIndex, options: normalizedOptions });
|
|
127
|
+
Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey as string);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Context 参数装饰器
|
|
133
|
+
* 用于在控制器方法中注入当前请求的 Context 对象
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* @GET('/users/:id')
|
|
138
|
+
* public async getUser(@Param('id') id: string, @Context() context: Context) {
|
|
139
|
+
* // 可以直接访问 context
|
|
140
|
+
* const header = context.getHeader('Authorization');
|
|
141
|
+
* return { id, header };
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function Context() {
|
|
146
|
+
return createParamDecorator(ParamType.CONTEXT);
|
|
147
|
+
}
|
|
148
|
+
|
|
75
149
|
/**
|
|
76
150
|
* 获取参数元数据
|
|
77
151
|
* @param target - 目标对象
|
package/src/controller/index.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
1
|
+
export {
|
|
2
|
+
Body,
|
|
3
|
+
Query,
|
|
4
|
+
QueryMap,
|
|
5
|
+
Param,
|
|
6
|
+
Header,
|
|
7
|
+
HeaderMap,
|
|
8
|
+
Context,
|
|
9
|
+
getParamMetadata,
|
|
10
|
+
ParamType,
|
|
11
|
+
} from './decorators';
|
|
12
|
+
export type {
|
|
13
|
+
ParamMetadata,
|
|
14
|
+
QueryMapOptions,
|
|
15
|
+
HeaderMapOptions,
|
|
16
|
+
} from './decorators';
|
|
3
17
|
export { ParamBinder } from './param-binder';
|
|
4
18
|
export { Controller, ControllerRegistry } from './controller';
|
|
5
19
|
export type { ControllerMetadata } from './controller';
|