@nattyjs/core 0.0.1-beta.64 → 0.0.1-beta.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,8 +2,13 @@
2
2
 
3
3
  const common = require('@nattyjs/common');
4
4
  const pathToRegexp = require('path-to-regexp');
5
+ const path = require('node:path');
5
6
  require('reflect-metadata');
6
7
 
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
9
+
10
+ const path__default = /*#__PURE__*/_interopDefaultCompat(path);
11
+
7
12
  function defineNattyConfig(config) {
8
13
  return config;
9
14
  }
@@ -54,6 +59,7 @@ function Delete() {
54
59
  };
55
60
  }
56
61
 
62
+ const HTTP_METHODS = ["get", "post", "put", "delete"];
57
63
  const _StaticContainer = class {
58
64
  setup(types) {
59
65
  _StaticContainer.types = types;
@@ -65,14 +71,56 @@ const nattyContainer = new class {
65
71
  constructor() {
66
72
  this.container = /* @__PURE__ */ new Map();
67
73
  this.containerState = /* @__PURE__ */ new Map();
74
+ this.compiledRoutes = {};
68
75
  }
69
76
  get types() {
70
77
  return common.commonContainer.types;
71
78
  }
72
79
  setup(config, routes, resolver) {
73
80
  this.config = config;
74
- this.routes = routes;
75
81
  this.resolver = resolver;
82
+ this.applyRouteSnapshot(routes);
83
+ }
84
+ replaceRoutes(manifest) {
85
+ if (manifest.resolver)
86
+ this.resolver = manifest.resolver;
87
+ this.applyRouteSnapshot(manifest.routes);
88
+ }
89
+ applyRouteSnapshot(routes) {
90
+ this.routes = routes;
91
+ this.compiledRoutes = this.createCompiledRoutes(routes);
92
+ }
93
+ createCompiledRoutes(routes) {
94
+ const compiledRoutes = {};
95
+ for (const method of HTTP_METHODS)
96
+ compiledRoutes[method] = [];
97
+ for (const [rootPath, routeConfig] of Object.entries(routes)) {
98
+ for (const method of HTTP_METHODS) {
99
+ const childRoutes = routeConfig[method];
100
+ if (!childRoutes)
101
+ continue;
102
+ for (const [childRoutePath, routeInfo] of Object.entries(childRoutes)) {
103
+ const childPath = this.normalizeChildPath(childRoutePath);
104
+ const configuredRoutePath = `${rootPath}${childPath}`;
105
+ compiledRoutes[method].push({
106
+ requestMethod: method,
107
+ configuredRoutePath,
108
+ matcher: pathToRegexp.match(configuredRoutePath, { decode: decodeURIComponent }),
109
+ routeConfig,
110
+ methodInfo: routeInfo
111
+ });
112
+ }
113
+ }
114
+ }
115
+ return compiledRoutes;
116
+ }
117
+ normalizeChildPath(childRoutePath) {
118
+ if (childRoutePath.indexOf("/") === 0)
119
+ return childRoutePath === "/" ? BLANK : childRoutePath;
120
+ return `/${childRoutePath}`;
121
+ }
122
+ getCompiledRoutes(method) {
123
+ return this.compiledRoutes[method] || [];
76
124
  }
77
125
  getTypes() {
78
126
  return StaticContainer.types;
@@ -163,8 +211,10 @@ function setupLegacyTypes(types) {
163
211
  function defaultResolver(path) {
164
212
  if (typeof path === "function")
165
213
  return path();
166
- if (typeof path === "string")
167
- return require(path);
214
+ if (typeof path === "string") {
215
+ const normalizedPath = path.startsWith("/") ? `.${path}` : path;
216
+ return require(normalizedPath);
217
+ }
168
218
  return {};
169
219
  }
170
220
  function initializeModule(config) {
@@ -514,6 +564,15 @@ class BaseResponse {
514
564
  notFound() {
515
565
  throw new HttpNotFoundException({});
516
566
  }
567
+ normalizeHttpResponse(result) {
568
+ if (result instanceof HttpResponse)
569
+ return result;
570
+ if (result instanceof HttpException)
571
+ return result.getResponse();
572
+ if (result && typeof result === "object" && ("status" in result || "body" in result || "headers" in result || "cookies" in result || "isBuffer" in result))
573
+ return new HttpResponse(result);
574
+ return result;
575
+ }
517
576
  }
518
577
 
519
578
  function getTypedErrorMessage(type, value) {
@@ -848,8 +907,8 @@ class RouteParser extends ParameterTypeConverter {
848
907
  get httpMethod() {
849
908
  return this.httpContext.request.method.toLowerCase();
850
909
  }
851
- get appRoutes() {
852
- return nattyContainer.routes;
910
+ get compiledRoutes() {
911
+ return nattyContainer.getCompiledRoutes(this.httpMethod);
853
912
  }
854
913
  init() {
855
914
  const isMatched = this.matchRoute();
@@ -862,47 +921,46 @@ class RouteParser extends ParameterTypeConverter {
862
921
  return controllerInfo[name];
863
922
  }
864
923
  matchRoute() {
865
- let isMatched = false;
866
- const requestPathname = this.getRequestPathname();
867
- for (const [key, value] of Object.entries(this.appRoutes)) {
868
- const rootPath = key;
869
- const routeConfig = value;
870
- const childRoutes = routeConfig[this.httpMethod];
871
- if (childRoutes) {
872
- for (const [key2, value2] of Object.entries(childRoutes)) {
873
- const childPath = key2.indexOf(common.RIGHT_SLASH) == 0 ? key2 === common.RIGHT_SLASH ? common.BLANK : key2 : `/${key2}`;
874
- const methodInfo = value2;
875
- const configuredRoutePath = `${rootPath}${childPath}`;
876
- const routeMatch = pathToRegexp.match(`${rootPath}${childPath}`, { decode: decodeURIComponent });
877
- const matched = routeMatch(requestPathname);
878
- if (matched) {
879
- isMatched = true;
880
- this.routeInfo = {
881
- path: `${common.commonContainer.nattyConfig.api.rootPath}/${requestPathname}`,
882
- configuredRoutePath: `${common.commonContainer.nattyConfig.api.rootPath}/${configuredRoutePath}`,
883
- controller: this.getController(routeConfig),
884
- parameters: routeConfig.parameters,
885
- methodInfo,
886
- params: this.convert(methodInfo, matched.params),
887
- queryParams: this.getQueryParams()
888
- };
889
- break;
890
- }
891
- }
924
+ const requestUrl = this.getRequestUrl();
925
+ const requestPathname = this.getRequestPathname(requestUrl);
926
+ for (const route of this.compiledRoutes) {
927
+ const matched = route.matcher(requestPathname);
928
+ if (matched) {
929
+ this.routeInfo = {
930
+ path: `${common.commonContainer.nattyConfig.api.rootPath}/${requestPathname}`,
931
+ configuredRoutePath: `${common.commonContainer.nattyConfig.api.rootPath}/${route.configuredRoutePath}`,
932
+ controller: this.getController(route.routeConfig),
933
+ parameters: route.routeConfig.parameters,
934
+ methodInfo: route.methodInfo,
935
+ params: this.convert(route.methodInfo, matched.params),
936
+ queryParams: this.getQueryParams(requestUrl)
937
+ };
938
+ return true;
892
939
  }
893
940
  }
894
- return isMatched;
895
- }
896
- getRequestPathname() {
897
- const apiRootPath = common.commonContainer.nattyConfig.api.rootPath;
898
- const url = new URL(this.httpContext.request.url);
899
- let splitUrl = url.pathname.split(`${apiRootPath}/`);
900
- if (splitUrl.length > 1)
901
- return splitUrl[1];
902
- return url.pathname;
903
- }
904
- getQueryParams() {
905
- const url = new URL(this.httpContext.request.url);
941
+ return false;
942
+ }
943
+ getRequestUrl() {
944
+ if (!this.parsedRequestUrl)
945
+ this.parsedRequestUrl = new URL(this.httpContext.request.url);
946
+ return this.parsedRequestUrl;
947
+ }
948
+ getRequestPathname(url) {
949
+ const apiRootPath = String(common.commonContainer.nattyConfig.api.rootPath || "").replace(/^\/+|\/+$/g, "");
950
+ const normalizedPathname = url.pathname.replace(/^\/+/, "");
951
+ if (!apiRootPath) {
952
+ return normalizedPathname;
953
+ }
954
+ if (normalizedPathname === apiRootPath) {
955
+ return "";
956
+ }
957
+ const apiPrefix = `${apiRootPath}/`;
958
+ if (normalizedPathname.startsWith(apiPrefix)) {
959
+ return normalizedPathname.slice(apiPrefix.length);
960
+ }
961
+ return normalizedPathname;
962
+ }
963
+ getQueryParams(url) {
906
964
  const queryParams = {};
907
965
  for (const param of url.searchParams.keys())
908
966
  queryParams[param] = url.searchParams.get(param);
@@ -913,8 +971,10 @@ class RouteParser extends ParameterTypeConverter {
913
971
  const decoratorStateContainer = new class {
914
972
  constructor() {
915
973
  this.controllerConfig = {};
974
+ this.controllerSources = /* @__PURE__ */ new Map();
975
+ this.sourceControllers = /* @__PURE__ */ new Map();
916
976
  }
917
- register(params, type, additionalConfig) {
977
+ register(params, type, additionalConfig, sourceFile) {
918
978
  const name = params.target.name || params.target.constructor.name;
919
979
  let controllerInfo = this.controllerConfig[name] || null;
920
980
  let controllerMethodInfo = null;
@@ -927,6 +987,9 @@ const decoratorStateContainer = new class {
927
987
  controllerMethodInfo[type] = additionalConfig;
928
988
  } else
929
989
  controllerInfo.config[type] = additionalConfig;
990
+ if (sourceFile) {
991
+ this.trackControllerSource(name, sourceFile);
992
+ }
930
993
  }
931
994
  getInfo(controllerName, methodName, type) {
932
995
  const controllerInfo = this.controllerConfig[controllerName];
@@ -939,6 +1002,55 @@ const decoratorStateContainer = new class {
939
1002
  methodConfig = methodInfo[type];
940
1003
  return { controllerConfig, methodConfig };
941
1004
  }
1005
+ clearFromSource(sourceFile) {
1006
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
1007
+ const controllerNames = Array.from(
1008
+ this.sourceControllers.get(normalizedSourceFile) || []
1009
+ );
1010
+ this.clearControllers(controllerNames);
1011
+ this.sourceControllers.delete(normalizedSourceFile);
1012
+ return controllerNames;
1013
+ }
1014
+ clearControllers(controllerNames) {
1015
+ const removedControllers = [];
1016
+ for (const controllerName of controllerNames) {
1017
+ const previousSource = this.controllerSources.get(controllerName);
1018
+ delete this.controllerConfig[controllerName];
1019
+ this.controllerSources.delete(controllerName);
1020
+ removedControllers.push(controllerName);
1021
+ if (previousSource) {
1022
+ const controllers = this.sourceControllers.get(previousSource);
1023
+ controllers?.delete(controllerName);
1024
+ if (controllers && controllers.size === 0) {
1025
+ this.sourceControllers.delete(previousSource);
1026
+ }
1027
+ }
1028
+ }
1029
+ return removedControllers;
1030
+ }
1031
+ reset() {
1032
+ this.controllerConfig = {};
1033
+ this.controllerSources.clear();
1034
+ this.sourceControllers.clear();
1035
+ }
1036
+ trackControllerSource(controllerName, sourceFile) {
1037
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
1038
+ const previousSource = this.controllerSources.get(controllerName);
1039
+ if (previousSource && previousSource !== normalizedSourceFile) {
1040
+ this.sourceControllers.get(previousSource)?.delete(controllerName);
1041
+ if (this.sourceControllers.get(previousSource)?.size === 0) {
1042
+ this.sourceControllers.delete(previousSource);
1043
+ }
1044
+ }
1045
+ this.controllerSources.set(controllerName, normalizedSourceFile);
1046
+ if (!this.sourceControllers.has(normalizedSourceFile)) {
1047
+ this.sourceControllers.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1048
+ }
1049
+ this.sourceControllers.get(normalizedSourceFile)?.add(controllerName);
1050
+ }
1051
+ normalizeFilePath(filePath) {
1052
+ return path__default.resolve(filePath).replace(/\\/g, "/");
1053
+ }
942
1054
  }();
943
1055
 
944
1056
  var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
@@ -1239,25 +1351,47 @@ class NattyScope {
1239
1351
  }
1240
1352
  }
1241
1353
 
1354
+ function normalizeFilePath$1(filePath) {
1355
+ return path__default.resolve(filePath).replace(/\\/g, "/");
1356
+ }
1242
1357
  class NattyContainer {
1243
1358
  constructor() {
1244
1359
  this.regs = /* @__PURE__ */ new Map();
1245
1360
  this.singletons = /* @__PURE__ */ new Map();
1361
+ this.tokenSources = /* @__PURE__ */ new Map();
1362
+ this.sourceTokens = /* @__PURE__ */ new Map();
1363
+ this.metadataKeySources = /* @__PURE__ */ new Map();
1364
+ this.sourceMetadataKeys = /* @__PURE__ */ new Map();
1246
1365
  }
1247
- register(token, desc) {
1366
+ register(token, desc, sourceFile) {
1248
1367
  this.regs.set(token, desc);
1368
+ if (sourceFile) {
1369
+ this.trackTokenSource(token, sourceFile);
1370
+ }
1249
1371
  }
1250
- addTransient(token, useClass, useFactory) {
1251
- this.register(token, { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory });
1372
+ addTransient(token, useClass, useFactory, sourceFile) {
1373
+ this.register(
1374
+ token,
1375
+ { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory },
1376
+ sourceFile
1377
+ );
1252
1378
  }
1253
- addScoped(token, useClass, useFactory) {
1254
- this.register(token, { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory });
1379
+ addScoped(token, useClass, useFactory, sourceFile) {
1380
+ this.register(
1381
+ token,
1382
+ { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory },
1383
+ sourceFile
1384
+ );
1255
1385
  }
1256
- addSingleton(token, useClass, useFactory) {
1257
- this.register(token, { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory });
1386
+ addSingleton(token, useClass, useFactory, sourceFile) {
1387
+ this.register(
1388
+ token,
1389
+ { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory },
1390
+ sourceFile
1391
+ );
1258
1392
  }
1259
- addInstance(token, value) {
1260
- this.register(token, { lifetime: Lifetime.Singleton, useValue: value });
1393
+ addInstance(token, value, sourceFile) {
1394
+ this.register(token, { lifetime: Lifetime.Singleton, useValue: value }, sourceFile);
1261
1395
  this.singletons.set(token, value);
1262
1396
  }
1263
1397
  createScope() {
@@ -1302,6 +1436,57 @@ class NattyContainer {
1302
1436
  _getDescriptor(token) {
1303
1437
  return this.regs.get(token);
1304
1438
  }
1439
+ trackMetadataKey(sourceFile, key) {
1440
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1441
+ const previousSource = this.metadataKeySources.get(key);
1442
+ if (previousSource && previousSource !== normalizedSourceFile) {
1443
+ this.sourceMetadataKeys.get(previousSource)?.delete(key);
1444
+ if (this.sourceMetadataKeys.get(previousSource)?.size === 0) {
1445
+ this.sourceMetadataKeys.delete(previousSource);
1446
+ }
1447
+ }
1448
+ this.metadataKeySources.set(key, normalizedSourceFile);
1449
+ if (!this.sourceMetadataKeys.has(normalizedSourceFile)) {
1450
+ this.sourceMetadataKeys.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1451
+ }
1452
+ this.sourceMetadataKeys.get(normalizedSourceFile)?.add(key);
1453
+ }
1454
+ clearRegistrationsFromSource(sourceFile) {
1455
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1456
+ const tokens = Array.from(this.sourceTokens.get(normalizedSourceFile) || []);
1457
+ const metadataKeys = Array.from(
1458
+ this.sourceMetadataKeys.get(normalizedSourceFile) || []
1459
+ );
1460
+ for (const token of tokens) {
1461
+ this.regs.delete(token);
1462
+ this.singletons.delete(token);
1463
+ this.tokenSources.delete(token);
1464
+ }
1465
+ for (const metadataKey of metadataKeys) {
1466
+ this.metadataKeySources.delete(metadataKey);
1467
+ }
1468
+ this.sourceTokens.delete(normalizedSourceFile);
1469
+ this.sourceMetadataKeys.delete(normalizedSourceFile);
1470
+ return {
1471
+ metadataKeys,
1472
+ tokens
1473
+ };
1474
+ }
1475
+ trackTokenSource(token, sourceFile) {
1476
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1477
+ const previousSource = this.tokenSources.get(token);
1478
+ if (previousSource && previousSource !== normalizedSourceFile) {
1479
+ this.sourceTokens.get(previousSource)?.delete(token);
1480
+ if (this.sourceTokens.get(previousSource)?.size === 0) {
1481
+ this.sourceTokens.delete(previousSource);
1482
+ }
1483
+ }
1484
+ this.tokenSources.set(token, normalizedSourceFile);
1485
+ if (!this.sourceTokens.has(normalizedSourceFile)) {
1486
+ this.sourceTokens.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1487
+ }
1488
+ this.sourceTokens.get(normalizedSourceFile)?.add(token);
1489
+ }
1305
1490
  }
1306
1491
 
1307
1492
  const nattyServiceResolver = new NattyContainer();
@@ -1407,14 +1592,14 @@ class Resolver extends RequestProcessor {
1407
1592
  if (onException) {
1408
1593
  const instance = this.resolveClass(onException);
1409
1594
  if (instance.onException)
1410
- result = instance.onException({
1595
+ result = this.normalizeHttpResponse(instance.onException({
1411
1596
  error: ex,
1412
1597
  request: this.httpContext.request,
1413
1598
  routeInfo: this.routeInfo
1414
- });
1599
+ }));
1415
1600
  } else
1416
1601
  result = new HttpException({
1417
- body: getPreResponseBody({ message: ex.message, stack: ex.stack }),
1602
+ body: { message: ex.message, stack: ex.stack },
1418
1603
  status: HttpStatusCode.serverError
1419
1604
  }).getResponse();
1420
1605
  }
@@ -1442,14 +1627,17 @@ class RequestHandler extends Resolver {
1442
1627
  if (onException) {
1443
1628
  const instance = this.resolveClass(onException);
1444
1629
  if (instance.onException)
1445
- return instance.onException({
1630
+ return this.normalizeHttpResponse(instance.onException({
1446
1631
  error: ex,
1447
1632
  request: this.httpContext.request,
1448
1633
  routeInfo: this.routeInfo
1449
- });
1634
+ }));
1450
1635
  } else
1451
1636
  return new HttpException({
1452
- body: ex,
1637
+ body: {
1638
+ message: ex?.message,
1639
+ stack: ex?.stack
1640
+ },
1453
1641
  status: HttpStatusCode.serverError
1454
1642
  });
1455
1643
  }
@@ -1462,20 +1650,59 @@ class HttpHandler {
1462
1650
  async processRequest(httpContext) {
1463
1651
  const requestProcessor = new RequestHandler(httpContext);
1464
1652
  const result = await requestProcessor.onRequest();
1465
- return result;
1653
+ if (result instanceof HttpResponse) {
1654
+ return result;
1655
+ }
1656
+ if (result instanceof HttpException) {
1657
+ return result.getResponse();
1658
+ }
1659
+ return new HttpResponse({ body: result });
1660
+ }
1661
+ }
1662
+
1663
+ const STACK_PATH_REGEX = /\bat (?:(?:.+?) \()?(.+):(\d+):(\d+)\)?$/;
1664
+ function normalizeFilePath(filePath) {
1665
+ return path__default.resolve(filePath).replace(/\\/g, "/");
1666
+ }
1667
+ function isInternalRegistrationFrame(filePath) {
1668
+ const normalizedPath = normalizeFilePath(filePath);
1669
+ return normalizedPath.includes("/packages/core/decorators/") || normalizedPath.includes("/packages/core/functions/") || normalizedPath.includes("/packages/core/domain/di/") || normalizedPath.includes("/packages/core/const/") || normalizedPath.includes("/packages/core/dist/") || normalizedPath.includes("/node_modules/@nattyjs/core/dist/");
1670
+ }
1671
+ function getRegistrationSourceFile() {
1672
+ const stack = new Error().stack;
1673
+ return getRegistrationSourceFileFromStack(stack);
1674
+ }
1675
+ function getRegistrationSourceFileFromStack(stack) {
1676
+ if (!stack) {
1677
+ return void 0;
1466
1678
  }
1679
+ const lines = stack.split("\n").slice(1);
1680
+ for (const line of lines) {
1681
+ const match = line.trim().match(STACK_PATH_REGEX);
1682
+ if (!match) {
1683
+ continue;
1684
+ }
1685
+ const filePath = normalizeFilePath(match[1]);
1686
+ if (!isInternalRegistrationFrame(filePath)) {
1687
+ return filePath;
1688
+ }
1689
+ }
1690
+ return void 0;
1467
1691
  }
1468
1692
 
1469
1693
  function injectable(options = {}) {
1470
1694
  return (targetConstructor) => {
1471
1695
  const lt = options.lifetime ?? Lifetime.Transient;
1696
+ const sourceFile = getRegistrationSourceFile();
1472
1697
  if (lt === Lifetime.Singleton)
1473
- nattyServiceResolver.addSingleton(targetConstructor);
1698
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1474
1699
  else if (lt === Lifetime.Scoped)
1475
- nattyServiceResolver.addScoped(targetConstructor);
1700
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1476
1701
  else
1477
- nattyServiceResolver.addTransient(targetConstructor);
1702
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1478
1703
  common.commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1704
+ if (sourceFile)
1705
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
1479
1706
  };
1480
1707
  }
1481
1708
 
@@ -1761,7 +1988,12 @@ const Results = {
1761
1988
  };
1762
1989
 
1763
1990
  function base(params, type, additionaConfig) {
1764
- decoratorStateContainer.register(params, type, additionaConfig);
1991
+ decoratorStateContainer.register(
1992
+ params,
1993
+ type,
1994
+ additionaConfig,
1995
+ getRegistrationSourceFile()
1996
+ );
1765
1997
  }
1766
1998
 
1767
1999
  function useFilter(config) {
@@ -1786,6 +2018,12 @@ function authenticationOnly() {
1786
2018
  };
1787
2019
  }
1788
2020
 
2021
+ function onException(exceptionFilter) {
2022
+ return function(target, propertyKey, descriptor) {
2023
+ base({ target, propertyKey, descriptor }, DecoratorType.onException, exceptionFilter);
2024
+ };
2025
+ }
2026
+
1789
2027
  function setEnvInfo(envTsDefinition, envValueInfo) {
1790
2028
  if (envTsDefinition && envValueInfo) {
1791
2029
  common.commonContainer.setEnvTsDefinition(envTsDefinition);
@@ -1833,30 +2071,40 @@ function getTokenTypeName(token) {
1833
2071
  return key.slice(separatorIndex + 1);
1834
2072
  }
1835
2073
 
1836
- function registerLifetime(targetConstructor, lifetime) {
2074
+ function registerLifetime(targetConstructor, sourceFile, lifetime) {
1837
2075
  if (lifetime === Lifetime.Singleton) {
1838
- nattyServiceResolver.addSingleton(targetConstructor);
2076
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1839
2077
  return;
1840
2078
  }
1841
2079
  if (lifetime === Lifetime.Scoped) {
1842
- nattyServiceResolver.addScoped(targetConstructor);
2080
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1843
2081
  return;
1844
2082
  }
1845
2083
  if (lifetime === Lifetime.Transient) {
1846
- nattyServiceResolver.addTransient(targetConstructor);
2084
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1847
2085
  }
1848
2086
  }
1849
- function registerAliasTypeName(token) {
2087
+ function registerAliasTypeName(token, sourceFile) {
1850
2088
  const typeName = getTokenTypeName(token);
1851
2089
  if (typeName) {
1852
2090
  common.commonContainer.setMetadata(typeName, token, "services");
2091
+ if (sourceFile)
2092
+ nattyServiceResolver.trackMetadataKey(sourceFile, typeName);
1853
2093
  }
1854
2094
  }
1855
2095
  function registerDiToken(targetConstructor, token, options = {}) {
2096
+ const sourceFile = getRegistrationSourceFile();
1856
2097
  common.commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1857
- registerAliasTypeName(token);
1858
- registerLifetime(targetConstructor, options.lifetime);
1859
- nattyServiceResolver.addTransient(token, void 0, (serviceProvider) => serviceProvider.get(targetConstructor));
2098
+ if (sourceFile)
2099
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
2100
+ registerAliasTypeName(token, sourceFile);
2101
+ registerLifetime(targetConstructor, sourceFile, options.lifetime);
2102
+ nattyServiceResolver.addTransient(
2103
+ token,
2104
+ void 0,
2105
+ (serviceProvider) => serviceProvider.get(targetConstructor),
2106
+ sourceFile
2107
+ );
1860
2108
  }
1861
2109
 
1862
2110
  function di(token, options = {}) {
@@ -1869,6 +2117,24 @@ function createServiceScope() {
1869
2117
  return nattyServiceResolver.createScope();
1870
2118
  }
1871
2119
 
2120
+ function clearHotReloadRegistrations(files) {
2121
+ for (const filePath of files || []) {
2122
+ const result = nattyServiceResolver.clearRegistrationsFromSource(filePath);
2123
+ decoratorStateContainer.clearFromSource(filePath);
2124
+ for (const metadataKey of result.metadataKeys) {
2125
+ common.commonContainer.deleteMetadataValue(metadataKey, "services");
2126
+ }
2127
+ }
2128
+ }
2129
+
2130
+ function clearHotReloadControllerMetadata(controllerNames) {
2131
+ decoratorStateContainer.clearControllers(controllerNames || []);
2132
+ }
2133
+
2134
+ function replaceRoutes(manifest) {
2135
+ nattyContainer.replaceRoutes(manifest);
2136
+ }
2137
+
1872
2138
  exports.$request = $request;
1873
2139
  exports.AbstractModelState = AbstractModelState;
1874
2140
  exports.AcceptedException = AcceptedException;
@@ -1915,6 +2181,8 @@ exports.anonymous = anonymous;
1915
2181
  exports.authenticationOnly = authenticationOnly;
1916
2182
  exports.authorize = authorize;
1917
2183
  exports.badRequest = badRequest;
2184
+ exports.clearHotReloadControllerMetadata = clearHotReloadControllerMetadata;
2185
+ exports.clearHotReloadRegistrations = clearHotReloadRegistrations;
1918
2186
  exports.conflict = conflict;
1919
2187
  exports.createServiceScope = createServiceScope;
1920
2188
  exports.created = created;
@@ -1934,12 +2202,14 @@ exports.noContent = noContent;
1934
2202
  exports.notFound = notFound;
1935
2203
  exports.notFoundWith = notFoundWith;
1936
2204
  exports.ok = ok;
2205
+ exports.onException = onException;
1937
2206
  exports.post = post;
1938
2207
  exports.problem = problem;
1939
2208
  exports.put = put;
1940
2209
  exports.redirect = redirect;
1941
2210
  exports.redirectPermanent = redirectPermanent;
1942
2211
  exports.registerDecorator = registerDecorator;
2212
+ exports.replaceRoutes = replaceRoutes;
1943
2213
  exports.route = route;
1944
2214
  exports.scoped = scoped;
1945
2215
  exports.setEnvInfo = setEnvInfo;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NattyConfig, UserIdentity } from '@nattyjs/common';
1
+ import { NattyConfig, UserIdentity, ClassType, ExceptionFilter } from '@nattyjs/common';
2
2
  import { GlobalConfig } from '@nattyjs/common/interfaces/global-config';
3
3
 
4
4
  interface DecoratorInfo$1 {
@@ -305,20 +305,31 @@ type Descriptor<T = any> = {
305
305
  useFactory?: Factory<T>;
306
306
  };
307
307
 
308
+ type ClearRegistrationsResult = {
309
+ metadataKeys: string[];
310
+ tokens: Token[];
311
+ };
308
312
  declare class NattyContainer implements ServiceProvider {
309
313
  private readonly regs;
310
314
  private readonly singletons;
311
- register<T>(token: Token<T>, desc: Descriptor<T>): void;
312
- addTransient<T>(token: Token<T>, useClass?: DIClassType<T>, useFactory?: Factory<T>): void;
313
- addScoped<T>(token: Token<T>, useClass?: DIClassType<T>, useFactory?: Factory<T>): void;
314
- addSingleton<T>(token: Token<T>, useClass?: DIClassType<T>, useFactory?: Factory<T>): void;
315
- addInstance<T>(token: Token<T>, value: T): void;
315
+ private readonly tokenSources;
316
+ private readonly sourceTokens;
317
+ private readonly metadataKeySources;
318
+ private readonly sourceMetadataKeys;
319
+ register<T>(token: Token<T>, desc: Descriptor<T>, sourceFile?: string): void;
320
+ addTransient<T>(token: Token<T>, useClass?: DIClassType<T>, useFactory?: Factory<T>, sourceFile?: string): void;
321
+ addScoped<T>(token: Token<T>, useClass?: DIClassType<T>, useFactory?: Factory<T>, sourceFile?: string): void;
322
+ addSingleton<T>(token: Token<T>, useClass?: DIClassType<T>, useFactory?: Factory<T>, sourceFile?: string): void;
323
+ addInstance<T>(token: Token<T>, value: T, sourceFile?: string): void;
316
324
  createScope(): NattyScope;
317
325
  set<T>(_token: Token<T>, _value: T): void;
318
326
  tryGet<T>(token: Token<T>): T | undefined;
319
327
  get<T>(token: Token<T>): T;
320
328
  construct<T>(Cls: DIClassType<T>, sp: ServiceProvider): T;
321
329
  _getDescriptor<T>(token: Token<T>): Descriptor<T> | undefined;
330
+ trackMetadataKey(sourceFile: string, key: string): void;
331
+ clearRegistrationsFromSource(sourceFile: string): ClearRegistrationsResult;
332
+ private trackTokenSource;
322
333
  }
323
334
 
324
335
  declare class NattyScope implements Scope {
@@ -391,6 +402,7 @@ declare abstract class BaseResponse {
391
402
  badRequest(): void;
392
403
  success(body: any): HttpResponse;
393
404
  notFound(): void;
405
+ protected normalizeHttpResponse(result: any): any;
394
406
  }
395
407
 
396
408
  declare abstract class ParameterTypeConverter extends BaseResponse {
@@ -402,9 +414,9 @@ declare abstract class ParameterTypeConverter extends BaseResponse {
402
414
  };
403
415
  get types(): TypesInfo;
404
416
  getObjectTypeInfo(typeName: string): {
405
- path?: string | any | Function;
406
- props?: Array<TypeInfo>;
407
- values?: Array<any>;
417
+ path?: any;
418
+ props?: TypeInfo[];
419
+ values?: any[];
408
420
  };
409
421
  isArrayType(typeName: string): boolean;
410
422
  getTypeName(typeName: string): string;
@@ -563,21 +575,21 @@ declare class StatusCodeResult<T = unknown> extends BaseResult implements IHttpR
563
575
 
564
576
  declare const Results: {
565
577
  readonly ok: <T = unknown>(value?: T, extras?: ResponseExtras) => OkResult<T>;
566
- readonly created: <T = unknown>(value?: T, extras?: ResponseExtras) => CreatedResult<T>;
567
- readonly createdWith: <T = unknown>(location: string, value?: T, extras?: ResponseExtras) => CreatedResult<T>;
568
- readonly createdAt: <T = unknown>(location: string, value?: T, extras?: ResponseExtras) => CreatedResult<T>;
569
- readonly accepted: <T = unknown>(value?: T, extras?: ResponseExtras) => AcceptedResult<T>;
578
+ readonly created: <T_1 = unknown>(value?: T_1, extras?: ResponseExtras) => CreatedResult<T_1>;
579
+ readonly createdWith: <T_2 = unknown>(location: string, value?: T_2, extras?: ResponseExtras) => CreatedResult<T_2>;
580
+ readonly createdAt: <T_3 = unknown>(location: string, value?: T_3, extras?: ResponseExtras) => CreatedResult<T_3>;
581
+ readonly accepted: <T_4 = unknown>(value?: T_4, extras?: ResponseExtras) => AcceptedResult<T_4>;
570
582
  readonly noContent: (extras?: ResponseExtras) => NoContentResult;
571
583
  readonly file: (buffer?: Buffer, extras?: ResponseExtras) => FileResult;
572
- readonly statusCode: <T = unknown>(status: number, value?: T, extras?: ResponseExtras) => StatusCodeResult<T>;
573
- readonly badRequest: <T = ExceptionTypeInfo>(value?: T, extras?: ResponseExtras) => BadRequestResult<T>;
584
+ readonly statusCode: <T_5 = unknown>(status: number, value?: T_5, extras?: ResponseExtras) => StatusCodeResult<T_5>;
585
+ readonly badRequest: <T_6 = ExceptionTypeInfo>(value?: T_6, extras?: ResponseExtras) => BadRequestResult<T_6>;
574
586
  readonly notFound: (extras?: ResponseExtras) => NotFoundResult<any>;
575
- readonly notFoundWith: <T = unknown>(value?: T, extras?: ResponseExtras) => NotFoundResult<T>;
576
- readonly unAuthorized: <T = ExceptionTypeInfo>(value?: T, extras?: ResponseExtras) => UnAuthorizedResult<T>;
577
- readonly forbid: <T = ExceptionTypeInfo>(value?: T, extras?: ResponseExtras) => ForbiddenAccessInfoResult<unknown>;
578
- readonly conflict: <T = unknown>(value?: T, extras?: ResponseExtras) => ConflictResult<T>;
579
- readonly unprocessableEntity: <T = unknown>(value?: T, extras?: ResponseExtras) => UnprocessableEntityResult<T>;
580
- readonly tooManyRequests: <T = unknown>(value?: T, retryAfterSeconds?: number, extras?: ResponseExtras) => TooManyRequestsResult<T>;
587
+ readonly notFoundWith: <T_7 = unknown>(value?: T_7, extras?: ResponseExtras) => NotFoundResult<T_7>;
588
+ readonly unAuthorized: <T_8 = ExceptionTypeInfo>(value?: T_8, extras?: ResponseExtras) => UnAuthorizedResult<T_8>;
589
+ readonly forbid: <T_9 = ExceptionTypeInfo>(value?: T_9, extras?: ResponseExtras) => ForbiddenAccessInfoResult<unknown>;
590
+ readonly conflict: <T_10 = unknown>(value?: T_10, extras?: ResponseExtras) => ConflictResult<T_10>;
591
+ readonly unprocessableEntity: <T_11 = unknown>(value?: T_11, extras?: ResponseExtras) => UnprocessableEntityResult<T_11>;
592
+ readonly tooManyRequests: <T_12 = unknown>(value?: T_12, retryAfterSeconds?: number, extras?: ResponseExtras) => TooManyRequestsResult<T_12>;
581
593
  readonly redirect: (location: string, extras?: ResponseExtras) => RedirectResult;
582
594
  readonly redirectPermanent: (location: string, extras?: ResponseExtras) => RedirectResult;
583
595
  readonly problem: (p: ProblemDetails, extras?: ResponseExtras) => ProblemResult;
@@ -644,6 +656,8 @@ declare function anonymous(): (target: any, propertyKey?: string, descriptor?: a
644
656
 
645
657
  declare function authenticationOnly(): (target: any, propertyKey?: string, descriptor?: any) => void;
646
658
 
659
+ declare function onException(exceptionFilter: ClassType<ExceptionFilter>): (target: any, propertyKey?: string, descriptor?: any) => void;
660
+
647
661
  declare function setEnvInfo(envTsDefinition: {
648
662
  [key: string]: string;
649
663
  }, envValueInfo: {
@@ -690,4 +704,17 @@ declare function registerType<T>(): unknown;
690
704
 
691
705
  declare function createServiceScope(): NattyScope;
692
706
 
693
- export { $request, AbstractModelState, AcceptedException, AcceptedResult, BadRequestResult, BaseController, BaseResult, BuildOptions, ClassTypeInfo, ConflictResult, CreateProblemDetail, CreatedResult, Delete, DiOptions, FileResult, ForbiddenAccessException, ForbiddenAccessInfoResult, HttpBadRequestException, HttpConflictException, HttpContext, HttpException, HttpHandler, HttpModule, HttpNotFoundException, HttpResponse, HttpStatusCode, HttpUnprocessableEntityException, Lifetime, MethodInfo$1 as MethodInfo, ModelBindingContext, NoContentResult, NotFoundResult, OkResult, ParameterInfo, ProblemDetailsException, ProblemResult, RedirectException, RedirectPermanentException, RedirectResult, ResponseExtras, Results, RetryAfter, RunOn, TooManyRequestsException, TooManyRequestsResult, TypeInfo$1 as TypeInfo, UnAuthorizedResult, UnauthorizedAccessException, UnprocessableEntityResult, ValidationProblemDetailsException, accepted, anonymous, authenticationOnly, authorize, badRequest, conflict, createServiceScope, created, createdAt, createdWith, defineNattyConfig, di, entityContainer, file, filter, forbiddenAccess, forbiddenAccessInfo, get, init, injectable, noContent, notFound, notFoundWith, ok, post, problem, put, redirect, redirectPermanent, registerDecorator, registerType, route, scoped, setEnvInfo, singleton, tooManyRequests, transient, unAuthorized, unprocessableEntity, useFilter, validationProblem };
707
+ declare function clearHotReloadRegistrations(files: string[]): void;
708
+
709
+ declare function clearHotReloadControllerMetadata(controllerNames: string[]): void;
710
+
711
+ interface RuntimeManifest {
712
+ routes: {
713
+ [key: string]: RouteConfig;
714
+ };
715
+ resolver?: (path: string) => any;
716
+ }
717
+
718
+ declare function replaceRoutes(manifest: RuntimeManifest): void;
719
+
720
+ export { $request, AbstractModelState, AcceptedException, AcceptedResult, BadRequestResult, BaseController, BaseResult, BuildOptions, ClassTypeInfo, ConflictResult, CreateProblemDetail, CreatedResult, Delete, DiOptions, FileResult, ForbiddenAccessException, ForbiddenAccessInfoResult, HttpBadRequestException, HttpConflictException, HttpContext, HttpException, HttpHandler, HttpModule, HttpNotFoundException, HttpResponse, HttpStatusCode, HttpUnprocessableEntityException, Lifetime, MethodInfo$1 as MethodInfo, ModelBindingContext, NoContentResult, NotFoundResult, OkResult, ParameterInfo, ProblemDetailsException, ProblemResult, RedirectException, RedirectPermanentException, RedirectResult, ResponseExtras, Results, RetryAfter, RunOn, RuntimeManifest, TooManyRequestsException, TooManyRequestsResult, TypeInfo$1 as TypeInfo, UnAuthorizedResult, UnauthorizedAccessException, UnprocessableEntityResult, ValidationProblemDetailsException, accepted, anonymous, authenticationOnly, authorize, badRequest, clearHotReloadControllerMetadata, clearHotReloadRegistrations, conflict, createServiceScope, created, createdAt, createdWith, defineNattyConfig, di, entityContainer, file, filter, forbiddenAccess, forbiddenAccessInfo, get, init, injectable, noContent, notFound, notFoundWith, ok, onException, post, problem, put, redirect, redirectPermanent, registerDecorator, registerType, replaceRoutes, route, scoped, setEnvInfo, singleton, tooManyRequests, transient, unAuthorized, unprocessableEntity, useFilter, validationProblem };
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { commonContainer, isObject, List, BLANK as BLANK$1, RIGHT_SLASH } from '@nattyjs/common';
1
+ import { commonContainer, isObject, List, BLANK as BLANK$1 } from '@nattyjs/common';
2
2
  import { match } from 'path-to-regexp';
3
+ import path from 'node:path';
3
4
  import 'reflect-metadata';
4
5
 
5
6
  function defineNattyConfig(config) {
@@ -52,6 +53,7 @@ function Delete() {
52
53
  };
53
54
  }
54
55
 
56
+ const HTTP_METHODS = ["get", "post", "put", "delete"];
55
57
  const _StaticContainer = class {
56
58
  setup(types) {
57
59
  _StaticContainer.types = types;
@@ -63,14 +65,56 @@ const nattyContainer = new class {
63
65
  constructor() {
64
66
  this.container = /* @__PURE__ */ new Map();
65
67
  this.containerState = /* @__PURE__ */ new Map();
68
+ this.compiledRoutes = {};
66
69
  }
67
70
  get types() {
68
71
  return commonContainer.types;
69
72
  }
70
73
  setup(config, routes, resolver) {
71
74
  this.config = config;
72
- this.routes = routes;
73
75
  this.resolver = resolver;
76
+ this.applyRouteSnapshot(routes);
77
+ }
78
+ replaceRoutes(manifest) {
79
+ if (manifest.resolver)
80
+ this.resolver = manifest.resolver;
81
+ this.applyRouteSnapshot(manifest.routes);
82
+ }
83
+ applyRouteSnapshot(routes) {
84
+ this.routes = routes;
85
+ this.compiledRoutes = this.createCompiledRoutes(routes);
86
+ }
87
+ createCompiledRoutes(routes) {
88
+ const compiledRoutes = {};
89
+ for (const method of HTTP_METHODS)
90
+ compiledRoutes[method] = [];
91
+ for (const [rootPath, routeConfig] of Object.entries(routes)) {
92
+ for (const method of HTTP_METHODS) {
93
+ const childRoutes = routeConfig[method];
94
+ if (!childRoutes)
95
+ continue;
96
+ for (const [childRoutePath, routeInfo] of Object.entries(childRoutes)) {
97
+ const childPath = this.normalizeChildPath(childRoutePath);
98
+ const configuredRoutePath = `${rootPath}${childPath}`;
99
+ compiledRoutes[method].push({
100
+ requestMethod: method,
101
+ configuredRoutePath,
102
+ matcher: match(configuredRoutePath, { decode: decodeURIComponent }),
103
+ routeConfig,
104
+ methodInfo: routeInfo
105
+ });
106
+ }
107
+ }
108
+ }
109
+ return compiledRoutes;
110
+ }
111
+ normalizeChildPath(childRoutePath) {
112
+ if (childRoutePath.indexOf("/") === 0)
113
+ return childRoutePath === "/" ? BLANK : childRoutePath;
114
+ return `/${childRoutePath}`;
115
+ }
116
+ getCompiledRoutes(method) {
117
+ return this.compiledRoutes[method] || [];
74
118
  }
75
119
  getTypes() {
76
120
  return StaticContainer.types;
@@ -161,8 +205,10 @@ function setupLegacyTypes(types) {
161
205
  function defaultResolver(path) {
162
206
  if (typeof path === "function")
163
207
  return path();
164
- if (typeof path === "string")
165
- return require(path);
208
+ if (typeof path === "string") {
209
+ const normalizedPath = path.startsWith("/") ? `.${path}` : path;
210
+ return require(normalizedPath);
211
+ }
166
212
  return {};
167
213
  }
168
214
  function initializeModule(config) {
@@ -512,6 +558,15 @@ class BaseResponse {
512
558
  notFound() {
513
559
  throw new HttpNotFoundException({});
514
560
  }
561
+ normalizeHttpResponse(result) {
562
+ if (result instanceof HttpResponse)
563
+ return result;
564
+ if (result instanceof HttpException)
565
+ return result.getResponse();
566
+ if (result && typeof result === "object" && ("status" in result || "body" in result || "headers" in result || "cookies" in result || "isBuffer" in result))
567
+ return new HttpResponse(result);
568
+ return result;
569
+ }
515
570
  }
516
571
 
517
572
  function getTypedErrorMessage(type, value) {
@@ -846,8 +901,8 @@ class RouteParser extends ParameterTypeConverter {
846
901
  get httpMethod() {
847
902
  return this.httpContext.request.method.toLowerCase();
848
903
  }
849
- get appRoutes() {
850
- return nattyContainer.routes;
904
+ get compiledRoutes() {
905
+ return nattyContainer.getCompiledRoutes(this.httpMethod);
851
906
  }
852
907
  init() {
853
908
  const isMatched = this.matchRoute();
@@ -860,47 +915,46 @@ class RouteParser extends ParameterTypeConverter {
860
915
  return controllerInfo[name];
861
916
  }
862
917
  matchRoute() {
863
- let isMatched = false;
864
- const requestPathname = this.getRequestPathname();
865
- for (const [key, value] of Object.entries(this.appRoutes)) {
866
- const rootPath = key;
867
- const routeConfig = value;
868
- const childRoutes = routeConfig[this.httpMethod];
869
- if (childRoutes) {
870
- for (const [key2, value2] of Object.entries(childRoutes)) {
871
- const childPath = key2.indexOf(RIGHT_SLASH) == 0 ? key2 === RIGHT_SLASH ? BLANK$1 : key2 : `/${key2}`;
872
- const methodInfo = value2;
873
- const configuredRoutePath = `${rootPath}${childPath}`;
874
- const routeMatch = match(`${rootPath}${childPath}`, { decode: decodeURIComponent });
875
- const matched = routeMatch(requestPathname);
876
- if (matched) {
877
- isMatched = true;
878
- this.routeInfo = {
879
- path: `${commonContainer.nattyConfig.api.rootPath}/${requestPathname}`,
880
- configuredRoutePath: `${commonContainer.nattyConfig.api.rootPath}/${configuredRoutePath}`,
881
- controller: this.getController(routeConfig),
882
- parameters: routeConfig.parameters,
883
- methodInfo,
884
- params: this.convert(methodInfo, matched.params),
885
- queryParams: this.getQueryParams()
886
- };
887
- break;
888
- }
889
- }
918
+ const requestUrl = this.getRequestUrl();
919
+ const requestPathname = this.getRequestPathname(requestUrl);
920
+ for (const route of this.compiledRoutes) {
921
+ const matched = route.matcher(requestPathname);
922
+ if (matched) {
923
+ this.routeInfo = {
924
+ path: `${commonContainer.nattyConfig.api.rootPath}/${requestPathname}`,
925
+ configuredRoutePath: `${commonContainer.nattyConfig.api.rootPath}/${route.configuredRoutePath}`,
926
+ controller: this.getController(route.routeConfig),
927
+ parameters: route.routeConfig.parameters,
928
+ methodInfo: route.methodInfo,
929
+ params: this.convert(route.methodInfo, matched.params),
930
+ queryParams: this.getQueryParams(requestUrl)
931
+ };
932
+ return true;
890
933
  }
891
934
  }
892
- return isMatched;
893
- }
894
- getRequestPathname() {
895
- const apiRootPath = commonContainer.nattyConfig.api.rootPath;
896
- const url = new URL(this.httpContext.request.url);
897
- let splitUrl = url.pathname.split(`${apiRootPath}/`);
898
- if (splitUrl.length > 1)
899
- return splitUrl[1];
900
- return url.pathname;
901
- }
902
- getQueryParams() {
903
- const url = new URL(this.httpContext.request.url);
935
+ return false;
936
+ }
937
+ getRequestUrl() {
938
+ if (!this.parsedRequestUrl)
939
+ this.parsedRequestUrl = new URL(this.httpContext.request.url);
940
+ return this.parsedRequestUrl;
941
+ }
942
+ getRequestPathname(url) {
943
+ const apiRootPath = String(commonContainer.nattyConfig.api.rootPath || "").replace(/^\/+|\/+$/g, "");
944
+ const normalizedPathname = url.pathname.replace(/^\/+/, "");
945
+ if (!apiRootPath) {
946
+ return normalizedPathname;
947
+ }
948
+ if (normalizedPathname === apiRootPath) {
949
+ return "";
950
+ }
951
+ const apiPrefix = `${apiRootPath}/`;
952
+ if (normalizedPathname.startsWith(apiPrefix)) {
953
+ return normalizedPathname.slice(apiPrefix.length);
954
+ }
955
+ return normalizedPathname;
956
+ }
957
+ getQueryParams(url) {
904
958
  const queryParams = {};
905
959
  for (const param of url.searchParams.keys())
906
960
  queryParams[param] = url.searchParams.get(param);
@@ -911,8 +965,10 @@ class RouteParser extends ParameterTypeConverter {
911
965
  const decoratorStateContainer = new class {
912
966
  constructor() {
913
967
  this.controllerConfig = {};
968
+ this.controllerSources = /* @__PURE__ */ new Map();
969
+ this.sourceControllers = /* @__PURE__ */ new Map();
914
970
  }
915
- register(params, type, additionalConfig) {
971
+ register(params, type, additionalConfig, sourceFile) {
916
972
  const name = params.target.name || params.target.constructor.name;
917
973
  let controllerInfo = this.controllerConfig[name] || null;
918
974
  let controllerMethodInfo = null;
@@ -925,6 +981,9 @@ const decoratorStateContainer = new class {
925
981
  controllerMethodInfo[type] = additionalConfig;
926
982
  } else
927
983
  controllerInfo.config[type] = additionalConfig;
984
+ if (sourceFile) {
985
+ this.trackControllerSource(name, sourceFile);
986
+ }
928
987
  }
929
988
  getInfo(controllerName, methodName, type) {
930
989
  const controllerInfo = this.controllerConfig[controllerName];
@@ -937,6 +996,55 @@ const decoratorStateContainer = new class {
937
996
  methodConfig = methodInfo[type];
938
997
  return { controllerConfig, methodConfig };
939
998
  }
999
+ clearFromSource(sourceFile) {
1000
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
1001
+ const controllerNames = Array.from(
1002
+ this.sourceControllers.get(normalizedSourceFile) || []
1003
+ );
1004
+ this.clearControllers(controllerNames);
1005
+ this.sourceControllers.delete(normalizedSourceFile);
1006
+ return controllerNames;
1007
+ }
1008
+ clearControllers(controllerNames) {
1009
+ const removedControllers = [];
1010
+ for (const controllerName of controllerNames) {
1011
+ const previousSource = this.controllerSources.get(controllerName);
1012
+ delete this.controllerConfig[controllerName];
1013
+ this.controllerSources.delete(controllerName);
1014
+ removedControllers.push(controllerName);
1015
+ if (previousSource) {
1016
+ const controllers = this.sourceControllers.get(previousSource);
1017
+ controllers?.delete(controllerName);
1018
+ if (controllers && controllers.size === 0) {
1019
+ this.sourceControllers.delete(previousSource);
1020
+ }
1021
+ }
1022
+ }
1023
+ return removedControllers;
1024
+ }
1025
+ reset() {
1026
+ this.controllerConfig = {};
1027
+ this.controllerSources.clear();
1028
+ this.sourceControllers.clear();
1029
+ }
1030
+ trackControllerSource(controllerName, sourceFile) {
1031
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
1032
+ const previousSource = this.controllerSources.get(controllerName);
1033
+ if (previousSource && previousSource !== normalizedSourceFile) {
1034
+ this.sourceControllers.get(previousSource)?.delete(controllerName);
1035
+ if (this.sourceControllers.get(previousSource)?.size === 0) {
1036
+ this.sourceControllers.delete(previousSource);
1037
+ }
1038
+ }
1039
+ this.controllerSources.set(controllerName, normalizedSourceFile);
1040
+ if (!this.sourceControllers.has(normalizedSourceFile)) {
1041
+ this.sourceControllers.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1042
+ }
1043
+ this.sourceControllers.get(normalizedSourceFile)?.add(controllerName);
1044
+ }
1045
+ normalizeFilePath(filePath) {
1046
+ return path.resolve(filePath).replace(/\\/g, "/");
1047
+ }
940
1048
  }();
941
1049
 
942
1050
  var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
@@ -1237,25 +1345,47 @@ class NattyScope {
1237
1345
  }
1238
1346
  }
1239
1347
 
1348
+ function normalizeFilePath$1(filePath) {
1349
+ return path.resolve(filePath).replace(/\\/g, "/");
1350
+ }
1240
1351
  class NattyContainer {
1241
1352
  constructor() {
1242
1353
  this.regs = /* @__PURE__ */ new Map();
1243
1354
  this.singletons = /* @__PURE__ */ new Map();
1355
+ this.tokenSources = /* @__PURE__ */ new Map();
1356
+ this.sourceTokens = /* @__PURE__ */ new Map();
1357
+ this.metadataKeySources = /* @__PURE__ */ new Map();
1358
+ this.sourceMetadataKeys = /* @__PURE__ */ new Map();
1244
1359
  }
1245
- register(token, desc) {
1360
+ register(token, desc, sourceFile) {
1246
1361
  this.regs.set(token, desc);
1362
+ if (sourceFile) {
1363
+ this.trackTokenSource(token, sourceFile);
1364
+ }
1247
1365
  }
1248
- addTransient(token, useClass, useFactory) {
1249
- this.register(token, { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory });
1366
+ addTransient(token, useClass, useFactory, sourceFile) {
1367
+ this.register(
1368
+ token,
1369
+ { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory },
1370
+ sourceFile
1371
+ );
1250
1372
  }
1251
- addScoped(token, useClass, useFactory) {
1252
- this.register(token, { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory });
1373
+ addScoped(token, useClass, useFactory, sourceFile) {
1374
+ this.register(
1375
+ token,
1376
+ { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory },
1377
+ sourceFile
1378
+ );
1253
1379
  }
1254
- addSingleton(token, useClass, useFactory) {
1255
- this.register(token, { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory });
1380
+ addSingleton(token, useClass, useFactory, sourceFile) {
1381
+ this.register(
1382
+ token,
1383
+ { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory },
1384
+ sourceFile
1385
+ );
1256
1386
  }
1257
- addInstance(token, value) {
1258
- this.register(token, { lifetime: Lifetime.Singleton, useValue: value });
1387
+ addInstance(token, value, sourceFile) {
1388
+ this.register(token, { lifetime: Lifetime.Singleton, useValue: value }, sourceFile);
1259
1389
  this.singletons.set(token, value);
1260
1390
  }
1261
1391
  createScope() {
@@ -1300,6 +1430,57 @@ class NattyContainer {
1300
1430
  _getDescriptor(token) {
1301
1431
  return this.regs.get(token);
1302
1432
  }
1433
+ trackMetadataKey(sourceFile, key) {
1434
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1435
+ const previousSource = this.metadataKeySources.get(key);
1436
+ if (previousSource && previousSource !== normalizedSourceFile) {
1437
+ this.sourceMetadataKeys.get(previousSource)?.delete(key);
1438
+ if (this.sourceMetadataKeys.get(previousSource)?.size === 0) {
1439
+ this.sourceMetadataKeys.delete(previousSource);
1440
+ }
1441
+ }
1442
+ this.metadataKeySources.set(key, normalizedSourceFile);
1443
+ if (!this.sourceMetadataKeys.has(normalizedSourceFile)) {
1444
+ this.sourceMetadataKeys.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1445
+ }
1446
+ this.sourceMetadataKeys.get(normalizedSourceFile)?.add(key);
1447
+ }
1448
+ clearRegistrationsFromSource(sourceFile) {
1449
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1450
+ const tokens = Array.from(this.sourceTokens.get(normalizedSourceFile) || []);
1451
+ const metadataKeys = Array.from(
1452
+ this.sourceMetadataKeys.get(normalizedSourceFile) || []
1453
+ );
1454
+ for (const token of tokens) {
1455
+ this.regs.delete(token);
1456
+ this.singletons.delete(token);
1457
+ this.tokenSources.delete(token);
1458
+ }
1459
+ for (const metadataKey of metadataKeys) {
1460
+ this.metadataKeySources.delete(metadataKey);
1461
+ }
1462
+ this.sourceTokens.delete(normalizedSourceFile);
1463
+ this.sourceMetadataKeys.delete(normalizedSourceFile);
1464
+ return {
1465
+ metadataKeys,
1466
+ tokens
1467
+ };
1468
+ }
1469
+ trackTokenSource(token, sourceFile) {
1470
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1471
+ const previousSource = this.tokenSources.get(token);
1472
+ if (previousSource && previousSource !== normalizedSourceFile) {
1473
+ this.sourceTokens.get(previousSource)?.delete(token);
1474
+ if (this.sourceTokens.get(previousSource)?.size === 0) {
1475
+ this.sourceTokens.delete(previousSource);
1476
+ }
1477
+ }
1478
+ this.tokenSources.set(token, normalizedSourceFile);
1479
+ if (!this.sourceTokens.has(normalizedSourceFile)) {
1480
+ this.sourceTokens.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1481
+ }
1482
+ this.sourceTokens.get(normalizedSourceFile)?.add(token);
1483
+ }
1303
1484
  }
1304
1485
 
1305
1486
  const nattyServiceResolver = new NattyContainer();
@@ -1405,14 +1586,14 @@ class Resolver extends RequestProcessor {
1405
1586
  if (onException) {
1406
1587
  const instance = this.resolveClass(onException);
1407
1588
  if (instance.onException)
1408
- result = instance.onException({
1589
+ result = this.normalizeHttpResponse(instance.onException({
1409
1590
  error: ex,
1410
1591
  request: this.httpContext.request,
1411
1592
  routeInfo: this.routeInfo
1412
- });
1593
+ }));
1413
1594
  } else
1414
1595
  result = new HttpException({
1415
- body: getPreResponseBody({ message: ex.message, stack: ex.stack }),
1596
+ body: { message: ex.message, stack: ex.stack },
1416
1597
  status: HttpStatusCode.serverError
1417
1598
  }).getResponse();
1418
1599
  }
@@ -1440,14 +1621,17 @@ class RequestHandler extends Resolver {
1440
1621
  if (onException) {
1441
1622
  const instance = this.resolveClass(onException);
1442
1623
  if (instance.onException)
1443
- return instance.onException({
1624
+ return this.normalizeHttpResponse(instance.onException({
1444
1625
  error: ex,
1445
1626
  request: this.httpContext.request,
1446
1627
  routeInfo: this.routeInfo
1447
- });
1628
+ }));
1448
1629
  } else
1449
1630
  return new HttpException({
1450
- body: ex,
1631
+ body: {
1632
+ message: ex?.message,
1633
+ stack: ex?.stack
1634
+ },
1451
1635
  status: HttpStatusCode.serverError
1452
1636
  });
1453
1637
  }
@@ -1460,20 +1644,59 @@ class HttpHandler {
1460
1644
  async processRequest(httpContext) {
1461
1645
  const requestProcessor = new RequestHandler(httpContext);
1462
1646
  const result = await requestProcessor.onRequest();
1463
- return result;
1647
+ if (result instanceof HttpResponse) {
1648
+ return result;
1649
+ }
1650
+ if (result instanceof HttpException) {
1651
+ return result.getResponse();
1652
+ }
1653
+ return new HttpResponse({ body: result });
1464
1654
  }
1465
1655
  }
1466
1656
 
1657
+ const STACK_PATH_REGEX = /\bat (?:(?:.+?) \()?(.+):(\d+):(\d+)\)?$/;
1658
+ function normalizeFilePath(filePath) {
1659
+ return path.resolve(filePath).replace(/\\/g, "/");
1660
+ }
1661
+ function isInternalRegistrationFrame(filePath) {
1662
+ const normalizedPath = normalizeFilePath(filePath);
1663
+ return normalizedPath.includes("/packages/core/decorators/") || normalizedPath.includes("/packages/core/functions/") || normalizedPath.includes("/packages/core/domain/di/") || normalizedPath.includes("/packages/core/const/") || normalizedPath.includes("/packages/core/dist/") || normalizedPath.includes("/node_modules/@nattyjs/core/dist/");
1664
+ }
1665
+ function getRegistrationSourceFile() {
1666
+ const stack = new Error().stack;
1667
+ return getRegistrationSourceFileFromStack(stack);
1668
+ }
1669
+ function getRegistrationSourceFileFromStack(stack) {
1670
+ if (!stack) {
1671
+ return void 0;
1672
+ }
1673
+ const lines = stack.split("\n").slice(1);
1674
+ for (const line of lines) {
1675
+ const match = line.trim().match(STACK_PATH_REGEX);
1676
+ if (!match) {
1677
+ continue;
1678
+ }
1679
+ const filePath = normalizeFilePath(match[1]);
1680
+ if (!isInternalRegistrationFrame(filePath)) {
1681
+ return filePath;
1682
+ }
1683
+ }
1684
+ return void 0;
1685
+ }
1686
+
1467
1687
  function injectable(options = {}) {
1468
1688
  return (targetConstructor) => {
1469
1689
  const lt = options.lifetime ?? Lifetime.Transient;
1690
+ const sourceFile = getRegistrationSourceFile();
1470
1691
  if (lt === Lifetime.Singleton)
1471
- nattyServiceResolver.addSingleton(targetConstructor);
1692
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1472
1693
  else if (lt === Lifetime.Scoped)
1473
- nattyServiceResolver.addScoped(targetConstructor);
1694
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1474
1695
  else
1475
- nattyServiceResolver.addTransient(targetConstructor);
1696
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1476
1697
  commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1698
+ if (sourceFile)
1699
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
1477
1700
  };
1478
1701
  }
1479
1702
 
@@ -1759,7 +1982,12 @@ const Results = {
1759
1982
  };
1760
1983
 
1761
1984
  function base(params, type, additionaConfig) {
1762
- decoratorStateContainer.register(params, type, additionaConfig);
1985
+ decoratorStateContainer.register(
1986
+ params,
1987
+ type,
1988
+ additionaConfig,
1989
+ getRegistrationSourceFile()
1990
+ );
1763
1991
  }
1764
1992
 
1765
1993
  function useFilter(config) {
@@ -1784,6 +2012,12 @@ function authenticationOnly() {
1784
2012
  };
1785
2013
  }
1786
2014
 
2015
+ function onException(exceptionFilter) {
2016
+ return function(target, propertyKey, descriptor) {
2017
+ base({ target, propertyKey, descriptor }, DecoratorType.onException, exceptionFilter);
2018
+ };
2019
+ }
2020
+
1787
2021
  function setEnvInfo(envTsDefinition, envValueInfo) {
1788
2022
  if (envTsDefinition && envValueInfo) {
1789
2023
  commonContainer.setEnvTsDefinition(envTsDefinition);
@@ -1831,30 +2065,40 @@ function getTokenTypeName(token) {
1831
2065
  return key.slice(separatorIndex + 1);
1832
2066
  }
1833
2067
 
1834
- function registerLifetime(targetConstructor, lifetime) {
2068
+ function registerLifetime(targetConstructor, sourceFile, lifetime) {
1835
2069
  if (lifetime === Lifetime.Singleton) {
1836
- nattyServiceResolver.addSingleton(targetConstructor);
2070
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1837
2071
  return;
1838
2072
  }
1839
2073
  if (lifetime === Lifetime.Scoped) {
1840
- nattyServiceResolver.addScoped(targetConstructor);
2074
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1841
2075
  return;
1842
2076
  }
1843
2077
  if (lifetime === Lifetime.Transient) {
1844
- nattyServiceResolver.addTransient(targetConstructor);
2078
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1845
2079
  }
1846
2080
  }
1847
- function registerAliasTypeName(token) {
2081
+ function registerAliasTypeName(token, sourceFile) {
1848
2082
  const typeName = getTokenTypeName(token);
1849
2083
  if (typeName) {
1850
2084
  commonContainer.setMetadata(typeName, token, "services");
2085
+ if (sourceFile)
2086
+ nattyServiceResolver.trackMetadataKey(sourceFile, typeName);
1851
2087
  }
1852
2088
  }
1853
2089
  function registerDiToken(targetConstructor, token, options = {}) {
2090
+ const sourceFile = getRegistrationSourceFile();
1854
2091
  commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1855
- registerAliasTypeName(token);
1856
- registerLifetime(targetConstructor, options.lifetime);
1857
- nattyServiceResolver.addTransient(token, void 0, (serviceProvider) => serviceProvider.get(targetConstructor));
2092
+ if (sourceFile)
2093
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
2094
+ registerAliasTypeName(token, sourceFile);
2095
+ registerLifetime(targetConstructor, sourceFile, options.lifetime);
2096
+ nattyServiceResolver.addTransient(
2097
+ token,
2098
+ void 0,
2099
+ (serviceProvider) => serviceProvider.get(targetConstructor),
2100
+ sourceFile
2101
+ );
1858
2102
  }
1859
2103
 
1860
2104
  function di(token, options = {}) {
@@ -1867,4 +2111,22 @@ function createServiceScope() {
1867
2111
  return nattyServiceResolver.createScope();
1868
2112
  }
1869
2113
 
1870
- export { $request, AbstractModelState, AcceptedException, AcceptedResult, BadRequestResult, BaseController, BaseResult, ConflictResult, CreateProblemDetail, CreatedResult, Delete, FileResult, ForbiddenAccessException, ForbiddenAccessInfoResult, HttpBadRequestException, HttpConflictException, HttpContext, HttpException, HttpHandler, HttpNotFoundException, HttpResponse, HttpStatusCode, HttpUnprocessableEntityException, Lifetime, ModelBindingContext, NoContentResult, NotFoundResult, OkResult, ProblemDetailsException, ProblemResult, RedirectException, RedirectPermanentException, RedirectResult, Results, RunOn, TooManyRequestsException, TooManyRequestsResult, UnAuthorizedResult, UnauthorizedAccessException, UnprocessableEntityResult, ValidationProblemDetailsException, accepted, anonymous, authenticationOnly, authorize, badRequest, conflict, createServiceScope, created, createdAt, createdWith, defineNattyConfig, di, entityContainer, file, filter, forbiddenAccess, forbiddenAccessInfo, get, init, injectable, noContent, notFound, notFoundWith, ok, post, problem, put, redirect, redirectPermanent, registerDecorator, route, scoped, setEnvInfo, singleton, tooManyRequests, transient, unAuthorized, unprocessableEntity, useFilter, validationProblem };
2114
+ function clearHotReloadRegistrations(files) {
2115
+ for (const filePath of files || []) {
2116
+ const result = nattyServiceResolver.clearRegistrationsFromSource(filePath);
2117
+ decoratorStateContainer.clearFromSource(filePath);
2118
+ for (const metadataKey of result.metadataKeys) {
2119
+ commonContainer.deleteMetadataValue(metadataKey, "services");
2120
+ }
2121
+ }
2122
+ }
2123
+
2124
+ function clearHotReloadControllerMetadata(controllerNames) {
2125
+ decoratorStateContainer.clearControllers(controllerNames || []);
2126
+ }
2127
+
2128
+ function replaceRoutes(manifest) {
2129
+ nattyContainer.replaceRoutes(manifest);
2130
+ }
2131
+
2132
+ export { $request, AbstractModelState, AcceptedException, AcceptedResult, BadRequestResult, BaseController, BaseResult, ConflictResult, CreateProblemDetail, CreatedResult, Delete, FileResult, ForbiddenAccessException, ForbiddenAccessInfoResult, HttpBadRequestException, HttpConflictException, HttpContext, HttpException, HttpHandler, HttpNotFoundException, HttpResponse, HttpStatusCode, HttpUnprocessableEntityException, Lifetime, ModelBindingContext, NoContentResult, NotFoundResult, OkResult, ProblemDetailsException, ProblemResult, RedirectException, RedirectPermanentException, RedirectResult, Results, RunOn, TooManyRequestsException, TooManyRequestsResult, UnAuthorizedResult, UnauthorizedAccessException, UnprocessableEntityResult, ValidationProblemDetailsException, accepted, anonymous, authenticationOnly, authorize, badRequest, clearHotReloadControllerMetadata, clearHotReloadRegistrations, conflict, createServiceScope, created, createdAt, createdWith, defineNattyConfig, di, entityContainer, file, filter, forbiddenAccess, forbiddenAccessInfo, get, init, injectable, noContent, notFound, notFoundWith, ok, onException, post, problem, put, redirect, redirectPermanent, registerDecorator, replaceRoutes, route, scoped, setEnvInfo, singleton, tooManyRequests, transient, unAuthorized, unprocessableEntity, useFilter, validationProblem };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nattyjs/core",
3
- "version": "0.0.1-beta.64",
3
+ "version": "0.0.1-beta.66",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "ajayojha",
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "reflect-metadata": "0.2.2",
19
19
  "path-to-regexp": "6.2.1",
20
- "@nattyjs/common": "0.0.1-beta.64"
20
+ "@nattyjs/common": "0.0.1-beta.66"
21
21
  },
22
22
  "devDependencies": {
23
23
  "unbuild": "1.2.1"