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

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;
@@ -848,8 +896,8 @@ class RouteParser extends ParameterTypeConverter {
848
896
  get httpMethod() {
849
897
  return this.httpContext.request.method.toLowerCase();
850
898
  }
851
- get appRoutes() {
852
- return nattyContainer.routes;
899
+ get compiledRoutes() {
900
+ return nattyContainer.getCompiledRoutes(this.httpMethod);
853
901
  }
854
902
  init() {
855
903
  const isMatched = this.matchRoute();
@@ -862,47 +910,38 @@ class RouteParser extends ParameterTypeConverter {
862
910
  return controllerInfo[name];
863
911
  }
864
912
  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
- }
913
+ const requestUrl = this.getRequestUrl();
914
+ const requestPathname = this.getRequestPathname(requestUrl);
915
+ for (const route of this.compiledRoutes) {
916
+ const matched = route.matcher(requestPathname);
917
+ if (matched) {
918
+ this.routeInfo = {
919
+ path: `${common.commonContainer.nattyConfig.api.rootPath}/${requestPathname}`,
920
+ configuredRoutePath: `${common.commonContainer.nattyConfig.api.rootPath}/${route.configuredRoutePath}`,
921
+ controller: this.getController(route.routeConfig),
922
+ parameters: route.routeConfig.parameters,
923
+ methodInfo: route.methodInfo,
924
+ params: this.convert(route.methodInfo, matched.params),
925
+ queryParams: this.getQueryParams(requestUrl)
926
+ };
927
+ return true;
892
928
  }
893
929
  }
894
- return isMatched;
930
+ return false;
931
+ }
932
+ getRequestUrl() {
933
+ if (!this.parsedRequestUrl)
934
+ this.parsedRequestUrl = new URL(this.httpContext.request.url);
935
+ return this.parsedRequestUrl;
895
936
  }
896
- getRequestPathname() {
937
+ getRequestPathname(url) {
897
938
  const apiRootPath = common.commonContainer.nattyConfig.api.rootPath;
898
- const url = new URL(this.httpContext.request.url);
899
939
  let splitUrl = url.pathname.split(`${apiRootPath}/`);
900
940
  if (splitUrl.length > 1)
901
941
  return splitUrl[1];
902
942
  return url.pathname;
903
943
  }
904
- getQueryParams() {
905
- const url = new URL(this.httpContext.request.url);
944
+ getQueryParams(url) {
906
945
  const queryParams = {};
907
946
  for (const param of url.searchParams.keys())
908
947
  queryParams[param] = url.searchParams.get(param);
@@ -913,8 +952,10 @@ class RouteParser extends ParameterTypeConverter {
913
952
  const decoratorStateContainer = new class {
914
953
  constructor() {
915
954
  this.controllerConfig = {};
955
+ this.controllerSources = /* @__PURE__ */ new Map();
956
+ this.sourceControllers = /* @__PURE__ */ new Map();
916
957
  }
917
- register(params, type, additionalConfig) {
958
+ register(params, type, additionalConfig, sourceFile) {
918
959
  const name = params.target.name || params.target.constructor.name;
919
960
  let controllerInfo = this.controllerConfig[name] || null;
920
961
  let controllerMethodInfo = null;
@@ -927,6 +968,9 @@ const decoratorStateContainer = new class {
927
968
  controllerMethodInfo[type] = additionalConfig;
928
969
  } else
929
970
  controllerInfo.config[type] = additionalConfig;
971
+ if (sourceFile) {
972
+ this.trackControllerSource(name, sourceFile);
973
+ }
930
974
  }
931
975
  getInfo(controllerName, methodName, type) {
932
976
  const controllerInfo = this.controllerConfig[controllerName];
@@ -939,6 +983,55 @@ const decoratorStateContainer = new class {
939
983
  methodConfig = methodInfo[type];
940
984
  return { controllerConfig, methodConfig };
941
985
  }
986
+ clearFromSource(sourceFile) {
987
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
988
+ const controllerNames = Array.from(
989
+ this.sourceControllers.get(normalizedSourceFile) || []
990
+ );
991
+ this.clearControllers(controllerNames);
992
+ this.sourceControllers.delete(normalizedSourceFile);
993
+ return controllerNames;
994
+ }
995
+ clearControllers(controllerNames) {
996
+ const removedControllers = [];
997
+ for (const controllerName of controllerNames) {
998
+ const previousSource = this.controllerSources.get(controllerName);
999
+ delete this.controllerConfig[controllerName];
1000
+ this.controllerSources.delete(controllerName);
1001
+ removedControllers.push(controllerName);
1002
+ if (previousSource) {
1003
+ const controllers = this.sourceControllers.get(previousSource);
1004
+ controllers?.delete(controllerName);
1005
+ if (controllers && controllers.size === 0) {
1006
+ this.sourceControllers.delete(previousSource);
1007
+ }
1008
+ }
1009
+ }
1010
+ return removedControllers;
1011
+ }
1012
+ reset() {
1013
+ this.controllerConfig = {};
1014
+ this.controllerSources.clear();
1015
+ this.sourceControllers.clear();
1016
+ }
1017
+ trackControllerSource(controllerName, sourceFile) {
1018
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
1019
+ const previousSource = this.controllerSources.get(controllerName);
1020
+ if (previousSource && previousSource !== normalizedSourceFile) {
1021
+ this.sourceControllers.get(previousSource)?.delete(controllerName);
1022
+ if (this.sourceControllers.get(previousSource)?.size === 0) {
1023
+ this.sourceControllers.delete(previousSource);
1024
+ }
1025
+ }
1026
+ this.controllerSources.set(controllerName, normalizedSourceFile);
1027
+ if (!this.sourceControllers.has(normalizedSourceFile)) {
1028
+ this.sourceControllers.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1029
+ }
1030
+ this.sourceControllers.get(normalizedSourceFile)?.add(controllerName);
1031
+ }
1032
+ normalizeFilePath(filePath) {
1033
+ return path__default.resolve(filePath).replace(/\\/g, "/");
1034
+ }
942
1035
  }();
943
1036
 
944
1037
  var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
@@ -1239,25 +1332,47 @@ class NattyScope {
1239
1332
  }
1240
1333
  }
1241
1334
 
1335
+ function normalizeFilePath$1(filePath) {
1336
+ return path__default.resolve(filePath).replace(/\\/g, "/");
1337
+ }
1242
1338
  class NattyContainer {
1243
1339
  constructor() {
1244
1340
  this.regs = /* @__PURE__ */ new Map();
1245
1341
  this.singletons = /* @__PURE__ */ new Map();
1342
+ this.tokenSources = /* @__PURE__ */ new Map();
1343
+ this.sourceTokens = /* @__PURE__ */ new Map();
1344
+ this.metadataKeySources = /* @__PURE__ */ new Map();
1345
+ this.sourceMetadataKeys = /* @__PURE__ */ new Map();
1246
1346
  }
1247
- register(token, desc) {
1347
+ register(token, desc, sourceFile) {
1248
1348
  this.regs.set(token, desc);
1349
+ if (sourceFile) {
1350
+ this.trackTokenSource(token, sourceFile);
1351
+ }
1249
1352
  }
1250
- addTransient(token, useClass, useFactory) {
1251
- this.register(token, { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory });
1353
+ addTransient(token, useClass, useFactory, sourceFile) {
1354
+ this.register(
1355
+ token,
1356
+ { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory },
1357
+ sourceFile
1358
+ );
1252
1359
  }
1253
- addScoped(token, useClass, useFactory) {
1254
- this.register(token, { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory });
1360
+ addScoped(token, useClass, useFactory, sourceFile) {
1361
+ this.register(
1362
+ token,
1363
+ { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory },
1364
+ sourceFile
1365
+ );
1255
1366
  }
1256
- addSingleton(token, useClass, useFactory) {
1257
- this.register(token, { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory });
1367
+ addSingleton(token, useClass, useFactory, sourceFile) {
1368
+ this.register(
1369
+ token,
1370
+ { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory },
1371
+ sourceFile
1372
+ );
1258
1373
  }
1259
- addInstance(token, value) {
1260
- this.register(token, { lifetime: Lifetime.Singleton, useValue: value });
1374
+ addInstance(token, value, sourceFile) {
1375
+ this.register(token, { lifetime: Lifetime.Singleton, useValue: value }, sourceFile);
1261
1376
  this.singletons.set(token, value);
1262
1377
  }
1263
1378
  createScope() {
@@ -1302,6 +1417,57 @@ class NattyContainer {
1302
1417
  _getDescriptor(token) {
1303
1418
  return this.regs.get(token);
1304
1419
  }
1420
+ trackMetadataKey(sourceFile, key) {
1421
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1422
+ const previousSource = this.metadataKeySources.get(key);
1423
+ if (previousSource && previousSource !== normalizedSourceFile) {
1424
+ this.sourceMetadataKeys.get(previousSource)?.delete(key);
1425
+ if (this.sourceMetadataKeys.get(previousSource)?.size === 0) {
1426
+ this.sourceMetadataKeys.delete(previousSource);
1427
+ }
1428
+ }
1429
+ this.metadataKeySources.set(key, normalizedSourceFile);
1430
+ if (!this.sourceMetadataKeys.has(normalizedSourceFile)) {
1431
+ this.sourceMetadataKeys.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1432
+ }
1433
+ this.sourceMetadataKeys.get(normalizedSourceFile)?.add(key);
1434
+ }
1435
+ clearRegistrationsFromSource(sourceFile) {
1436
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1437
+ const tokens = Array.from(this.sourceTokens.get(normalizedSourceFile) || []);
1438
+ const metadataKeys = Array.from(
1439
+ this.sourceMetadataKeys.get(normalizedSourceFile) || []
1440
+ );
1441
+ for (const token of tokens) {
1442
+ this.regs.delete(token);
1443
+ this.singletons.delete(token);
1444
+ this.tokenSources.delete(token);
1445
+ }
1446
+ for (const metadataKey of metadataKeys) {
1447
+ this.metadataKeySources.delete(metadataKey);
1448
+ }
1449
+ this.sourceTokens.delete(normalizedSourceFile);
1450
+ this.sourceMetadataKeys.delete(normalizedSourceFile);
1451
+ return {
1452
+ metadataKeys,
1453
+ tokens
1454
+ };
1455
+ }
1456
+ trackTokenSource(token, sourceFile) {
1457
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1458
+ const previousSource = this.tokenSources.get(token);
1459
+ if (previousSource && previousSource !== normalizedSourceFile) {
1460
+ this.sourceTokens.get(previousSource)?.delete(token);
1461
+ if (this.sourceTokens.get(previousSource)?.size === 0) {
1462
+ this.sourceTokens.delete(previousSource);
1463
+ }
1464
+ }
1465
+ this.tokenSources.set(token, normalizedSourceFile);
1466
+ if (!this.sourceTokens.has(normalizedSourceFile)) {
1467
+ this.sourceTokens.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1468
+ }
1469
+ this.sourceTokens.get(normalizedSourceFile)?.add(token);
1470
+ }
1305
1471
  }
1306
1472
 
1307
1473
  const nattyServiceResolver = new NattyContainer();
@@ -1414,7 +1580,7 @@ class Resolver extends RequestProcessor {
1414
1580
  });
1415
1581
  } else
1416
1582
  result = new HttpException({
1417
- body: getPreResponseBody({ message: ex.message, stack: ex.stack }),
1583
+ body: { message: ex.message, stack: ex.stack },
1418
1584
  status: HttpStatusCode.serverError
1419
1585
  }).getResponse();
1420
1586
  }
@@ -1449,7 +1615,10 @@ class RequestHandler extends Resolver {
1449
1615
  });
1450
1616
  } else
1451
1617
  return new HttpException({
1452
- body: ex,
1618
+ body: {
1619
+ message: ex?.message,
1620
+ stack: ex?.stack
1621
+ },
1453
1622
  status: HttpStatusCode.serverError
1454
1623
  });
1455
1624
  }
@@ -1462,20 +1631,59 @@ class HttpHandler {
1462
1631
  async processRequest(httpContext) {
1463
1632
  const requestProcessor = new RequestHandler(httpContext);
1464
1633
  const result = await requestProcessor.onRequest();
1465
- return result;
1634
+ if (result instanceof HttpResponse) {
1635
+ return result;
1636
+ }
1637
+ if (result instanceof HttpException) {
1638
+ return result.getResponse();
1639
+ }
1640
+ return new HttpResponse({ body: result });
1466
1641
  }
1467
1642
  }
1468
1643
 
1644
+ const STACK_PATH_REGEX = /\bat (?:(?:.+?) \()?(.+):(\d+):(\d+)\)?$/;
1645
+ function normalizeFilePath(filePath) {
1646
+ return path__default.resolve(filePath).replace(/\\/g, "/");
1647
+ }
1648
+ function isInternalRegistrationFrame(filePath) {
1649
+ const normalizedPath = normalizeFilePath(filePath);
1650
+ 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/");
1651
+ }
1652
+ function getRegistrationSourceFile() {
1653
+ const stack = new Error().stack;
1654
+ return getRegistrationSourceFileFromStack(stack);
1655
+ }
1656
+ function getRegistrationSourceFileFromStack(stack) {
1657
+ if (!stack) {
1658
+ return void 0;
1659
+ }
1660
+ const lines = stack.split("\n").slice(1);
1661
+ for (const line of lines) {
1662
+ const match = line.trim().match(STACK_PATH_REGEX);
1663
+ if (!match) {
1664
+ continue;
1665
+ }
1666
+ const filePath = normalizeFilePath(match[1]);
1667
+ if (!isInternalRegistrationFrame(filePath)) {
1668
+ return filePath;
1669
+ }
1670
+ }
1671
+ return void 0;
1672
+ }
1673
+
1469
1674
  function injectable(options = {}) {
1470
1675
  return (targetConstructor) => {
1471
1676
  const lt = options.lifetime ?? Lifetime.Transient;
1677
+ const sourceFile = getRegistrationSourceFile();
1472
1678
  if (lt === Lifetime.Singleton)
1473
- nattyServiceResolver.addSingleton(targetConstructor);
1679
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1474
1680
  else if (lt === Lifetime.Scoped)
1475
- nattyServiceResolver.addScoped(targetConstructor);
1681
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1476
1682
  else
1477
- nattyServiceResolver.addTransient(targetConstructor);
1683
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1478
1684
  common.commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1685
+ if (sourceFile)
1686
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
1479
1687
  };
1480
1688
  }
1481
1689
 
@@ -1761,7 +1969,12 @@ const Results = {
1761
1969
  };
1762
1970
 
1763
1971
  function base(params, type, additionaConfig) {
1764
- decoratorStateContainer.register(params, type, additionaConfig);
1972
+ decoratorStateContainer.register(
1973
+ params,
1974
+ type,
1975
+ additionaConfig,
1976
+ getRegistrationSourceFile()
1977
+ );
1765
1978
  }
1766
1979
 
1767
1980
  function useFilter(config) {
@@ -1786,6 +1999,12 @@ function authenticationOnly() {
1786
1999
  };
1787
2000
  }
1788
2001
 
2002
+ function onException(exceptionFilter) {
2003
+ return function(target, propertyKey, descriptor) {
2004
+ base({ target, propertyKey, descriptor }, DecoratorType.onException, exceptionFilter);
2005
+ };
2006
+ }
2007
+
1789
2008
  function setEnvInfo(envTsDefinition, envValueInfo) {
1790
2009
  if (envTsDefinition && envValueInfo) {
1791
2010
  common.commonContainer.setEnvTsDefinition(envTsDefinition);
@@ -1833,30 +2052,40 @@ function getTokenTypeName(token) {
1833
2052
  return key.slice(separatorIndex + 1);
1834
2053
  }
1835
2054
 
1836
- function registerLifetime(targetConstructor, lifetime) {
2055
+ function registerLifetime(targetConstructor, sourceFile, lifetime) {
1837
2056
  if (lifetime === Lifetime.Singleton) {
1838
- nattyServiceResolver.addSingleton(targetConstructor);
2057
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1839
2058
  return;
1840
2059
  }
1841
2060
  if (lifetime === Lifetime.Scoped) {
1842
- nattyServiceResolver.addScoped(targetConstructor);
2061
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1843
2062
  return;
1844
2063
  }
1845
2064
  if (lifetime === Lifetime.Transient) {
1846
- nattyServiceResolver.addTransient(targetConstructor);
2065
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1847
2066
  }
1848
2067
  }
1849
- function registerAliasTypeName(token) {
2068
+ function registerAliasTypeName(token, sourceFile) {
1850
2069
  const typeName = getTokenTypeName(token);
1851
2070
  if (typeName) {
1852
2071
  common.commonContainer.setMetadata(typeName, token, "services");
2072
+ if (sourceFile)
2073
+ nattyServiceResolver.trackMetadataKey(sourceFile, typeName);
1853
2074
  }
1854
2075
  }
1855
2076
  function registerDiToken(targetConstructor, token, options = {}) {
2077
+ const sourceFile = getRegistrationSourceFile();
1856
2078
  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));
2079
+ if (sourceFile)
2080
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
2081
+ registerAliasTypeName(token, sourceFile);
2082
+ registerLifetime(targetConstructor, sourceFile, options.lifetime);
2083
+ nattyServiceResolver.addTransient(
2084
+ token,
2085
+ void 0,
2086
+ (serviceProvider) => serviceProvider.get(targetConstructor),
2087
+ sourceFile
2088
+ );
1860
2089
  }
1861
2090
 
1862
2091
  function di(token, options = {}) {
@@ -1869,6 +2098,24 @@ function createServiceScope() {
1869
2098
  return nattyServiceResolver.createScope();
1870
2099
  }
1871
2100
 
2101
+ function clearHotReloadRegistrations(files) {
2102
+ for (const filePath of files || []) {
2103
+ const result = nattyServiceResolver.clearRegistrationsFromSource(filePath);
2104
+ decoratorStateContainer.clearFromSource(filePath);
2105
+ for (const metadataKey of result.metadataKeys) {
2106
+ common.commonContainer.deleteMetadataValue(metadataKey, "services");
2107
+ }
2108
+ }
2109
+ }
2110
+
2111
+ function clearHotReloadControllerMetadata(controllerNames) {
2112
+ decoratorStateContainer.clearControllers(controllerNames || []);
2113
+ }
2114
+
2115
+ function replaceRoutes(manifest) {
2116
+ nattyContainer.replaceRoutes(manifest);
2117
+ }
2118
+
1872
2119
  exports.$request = $request;
1873
2120
  exports.AbstractModelState = AbstractModelState;
1874
2121
  exports.AcceptedException = AcceptedException;
@@ -1915,6 +2162,8 @@ exports.anonymous = anonymous;
1915
2162
  exports.authenticationOnly = authenticationOnly;
1916
2163
  exports.authorize = authorize;
1917
2164
  exports.badRequest = badRequest;
2165
+ exports.clearHotReloadControllerMetadata = clearHotReloadControllerMetadata;
2166
+ exports.clearHotReloadRegistrations = clearHotReloadRegistrations;
1918
2167
  exports.conflict = conflict;
1919
2168
  exports.createServiceScope = createServiceScope;
1920
2169
  exports.created = created;
@@ -1934,12 +2183,14 @@ exports.noContent = noContent;
1934
2183
  exports.notFound = notFound;
1935
2184
  exports.notFoundWith = notFoundWith;
1936
2185
  exports.ok = ok;
2186
+ exports.onException = onException;
1937
2187
  exports.post = post;
1938
2188
  exports.problem = problem;
1939
2189
  exports.put = put;
1940
2190
  exports.redirect = redirect;
1941
2191
  exports.redirectPermanent = redirectPermanent;
1942
2192
  exports.registerDecorator = registerDecorator;
2193
+ exports.replaceRoutes = replaceRoutes;
1943
2194
  exports.route = route;
1944
2195
  exports.scoped = scoped;
1945
2196
  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 {
@@ -402,9 +413,9 @@ declare abstract class ParameterTypeConverter extends BaseResponse {
402
413
  };
403
414
  get types(): TypesInfo;
404
415
  getObjectTypeInfo(typeName: string): {
405
- path?: string | any | Function;
406
- props?: Array<TypeInfo>;
407
- values?: Array<any>;
416
+ path?: any;
417
+ props?: TypeInfo[];
418
+ values?: any[];
408
419
  };
409
420
  isArrayType(typeName: string): boolean;
410
421
  getTypeName(typeName: string): string;
@@ -563,21 +574,21 @@ declare class StatusCodeResult<T = unknown> extends BaseResult implements IHttpR
563
574
 
564
575
  declare const Results: {
565
576
  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>;
577
+ readonly created: <T_1 = unknown>(value?: T_1, extras?: ResponseExtras) => CreatedResult<T_1>;
578
+ readonly createdWith: <T_2 = unknown>(location: string, value?: T_2, extras?: ResponseExtras) => CreatedResult<T_2>;
579
+ readonly createdAt: <T_3 = unknown>(location: string, value?: T_3, extras?: ResponseExtras) => CreatedResult<T_3>;
580
+ readonly accepted: <T_4 = unknown>(value?: T_4, extras?: ResponseExtras) => AcceptedResult<T_4>;
570
581
  readonly noContent: (extras?: ResponseExtras) => NoContentResult;
571
582
  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>;
583
+ readonly statusCode: <T_5 = unknown>(status: number, value?: T_5, extras?: ResponseExtras) => StatusCodeResult<T_5>;
584
+ readonly badRequest: <T_6 = ExceptionTypeInfo>(value?: T_6, extras?: ResponseExtras) => BadRequestResult<T_6>;
574
585
  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>;
586
+ readonly notFoundWith: <T_7 = unknown>(value?: T_7, extras?: ResponseExtras) => NotFoundResult<T_7>;
587
+ readonly unAuthorized: <T_8 = ExceptionTypeInfo>(value?: T_8, extras?: ResponseExtras) => UnAuthorizedResult<T_8>;
588
+ readonly forbid: <T_9 = ExceptionTypeInfo>(value?: T_9, extras?: ResponseExtras) => ForbiddenAccessInfoResult<unknown>;
589
+ readonly conflict: <T_10 = unknown>(value?: T_10, extras?: ResponseExtras) => ConflictResult<T_10>;
590
+ readonly unprocessableEntity: <T_11 = unknown>(value?: T_11, extras?: ResponseExtras) => UnprocessableEntityResult<T_11>;
591
+ readonly tooManyRequests: <T_12 = unknown>(value?: T_12, retryAfterSeconds?: number, extras?: ResponseExtras) => TooManyRequestsResult<T_12>;
581
592
  readonly redirect: (location: string, extras?: ResponseExtras) => RedirectResult;
582
593
  readonly redirectPermanent: (location: string, extras?: ResponseExtras) => RedirectResult;
583
594
  readonly problem: (p: ProblemDetails, extras?: ResponseExtras) => ProblemResult;
@@ -644,6 +655,8 @@ declare function anonymous(): (target: any, propertyKey?: string, descriptor?: a
644
655
 
645
656
  declare function authenticationOnly(): (target: any, propertyKey?: string, descriptor?: any) => void;
646
657
 
658
+ declare function onException(exceptionFilter: ClassType<ExceptionFilter>): (target: any, propertyKey?: string, descriptor?: any) => void;
659
+
647
660
  declare function setEnvInfo(envTsDefinition: {
648
661
  [key: string]: string;
649
662
  }, envValueInfo: {
@@ -690,4 +703,17 @@ declare function registerType<T>(): unknown;
690
703
 
691
704
  declare function createServiceScope(): NattyScope;
692
705
 
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 };
706
+ declare function clearHotReloadRegistrations(files: string[]): void;
707
+
708
+ declare function clearHotReloadControllerMetadata(controllerNames: string[]): void;
709
+
710
+ interface RuntimeManifest {
711
+ routes: {
712
+ [key: string]: RouteConfig;
713
+ };
714
+ resolver?: (path: string) => any;
715
+ }
716
+
717
+ declare function replaceRoutes(manifest: RuntimeManifest): void;
718
+
719
+ 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;
@@ -846,8 +890,8 @@ class RouteParser extends ParameterTypeConverter {
846
890
  get httpMethod() {
847
891
  return this.httpContext.request.method.toLowerCase();
848
892
  }
849
- get appRoutes() {
850
- return nattyContainer.routes;
893
+ get compiledRoutes() {
894
+ return nattyContainer.getCompiledRoutes(this.httpMethod);
851
895
  }
852
896
  init() {
853
897
  const isMatched = this.matchRoute();
@@ -860,47 +904,38 @@ class RouteParser extends ParameterTypeConverter {
860
904
  return controllerInfo[name];
861
905
  }
862
906
  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
- }
907
+ const requestUrl = this.getRequestUrl();
908
+ const requestPathname = this.getRequestPathname(requestUrl);
909
+ for (const route of this.compiledRoutes) {
910
+ const matched = route.matcher(requestPathname);
911
+ if (matched) {
912
+ this.routeInfo = {
913
+ path: `${commonContainer.nattyConfig.api.rootPath}/${requestPathname}`,
914
+ configuredRoutePath: `${commonContainer.nattyConfig.api.rootPath}/${route.configuredRoutePath}`,
915
+ controller: this.getController(route.routeConfig),
916
+ parameters: route.routeConfig.parameters,
917
+ methodInfo: route.methodInfo,
918
+ params: this.convert(route.methodInfo, matched.params),
919
+ queryParams: this.getQueryParams(requestUrl)
920
+ };
921
+ return true;
890
922
  }
891
923
  }
892
- return isMatched;
924
+ return false;
925
+ }
926
+ getRequestUrl() {
927
+ if (!this.parsedRequestUrl)
928
+ this.parsedRequestUrl = new URL(this.httpContext.request.url);
929
+ return this.parsedRequestUrl;
893
930
  }
894
- getRequestPathname() {
931
+ getRequestPathname(url) {
895
932
  const apiRootPath = commonContainer.nattyConfig.api.rootPath;
896
- const url = new URL(this.httpContext.request.url);
897
933
  let splitUrl = url.pathname.split(`${apiRootPath}/`);
898
934
  if (splitUrl.length > 1)
899
935
  return splitUrl[1];
900
936
  return url.pathname;
901
937
  }
902
- getQueryParams() {
903
- const url = new URL(this.httpContext.request.url);
938
+ getQueryParams(url) {
904
939
  const queryParams = {};
905
940
  for (const param of url.searchParams.keys())
906
941
  queryParams[param] = url.searchParams.get(param);
@@ -911,8 +946,10 @@ class RouteParser extends ParameterTypeConverter {
911
946
  const decoratorStateContainer = new class {
912
947
  constructor() {
913
948
  this.controllerConfig = {};
949
+ this.controllerSources = /* @__PURE__ */ new Map();
950
+ this.sourceControllers = /* @__PURE__ */ new Map();
914
951
  }
915
- register(params, type, additionalConfig) {
952
+ register(params, type, additionalConfig, sourceFile) {
916
953
  const name = params.target.name || params.target.constructor.name;
917
954
  let controllerInfo = this.controllerConfig[name] || null;
918
955
  let controllerMethodInfo = null;
@@ -925,6 +962,9 @@ const decoratorStateContainer = new class {
925
962
  controllerMethodInfo[type] = additionalConfig;
926
963
  } else
927
964
  controllerInfo.config[type] = additionalConfig;
965
+ if (sourceFile) {
966
+ this.trackControllerSource(name, sourceFile);
967
+ }
928
968
  }
929
969
  getInfo(controllerName, methodName, type) {
930
970
  const controllerInfo = this.controllerConfig[controllerName];
@@ -937,6 +977,55 @@ const decoratorStateContainer = new class {
937
977
  methodConfig = methodInfo[type];
938
978
  return { controllerConfig, methodConfig };
939
979
  }
980
+ clearFromSource(sourceFile) {
981
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
982
+ const controllerNames = Array.from(
983
+ this.sourceControllers.get(normalizedSourceFile) || []
984
+ );
985
+ this.clearControllers(controllerNames);
986
+ this.sourceControllers.delete(normalizedSourceFile);
987
+ return controllerNames;
988
+ }
989
+ clearControllers(controllerNames) {
990
+ const removedControllers = [];
991
+ for (const controllerName of controllerNames) {
992
+ const previousSource = this.controllerSources.get(controllerName);
993
+ delete this.controllerConfig[controllerName];
994
+ this.controllerSources.delete(controllerName);
995
+ removedControllers.push(controllerName);
996
+ if (previousSource) {
997
+ const controllers = this.sourceControllers.get(previousSource);
998
+ controllers?.delete(controllerName);
999
+ if (controllers && controllers.size === 0) {
1000
+ this.sourceControllers.delete(previousSource);
1001
+ }
1002
+ }
1003
+ }
1004
+ return removedControllers;
1005
+ }
1006
+ reset() {
1007
+ this.controllerConfig = {};
1008
+ this.controllerSources.clear();
1009
+ this.sourceControllers.clear();
1010
+ }
1011
+ trackControllerSource(controllerName, sourceFile) {
1012
+ const normalizedSourceFile = this.normalizeFilePath(sourceFile);
1013
+ const previousSource = this.controllerSources.get(controllerName);
1014
+ if (previousSource && previousSource !== normalizedSourceFile) {
1015
+ this.sourceControllers.get(previousSource)?.delete(controllerName);
1016
+ if (this.sourceControllers.get(previousSource)?.size === 0) {
1017
+ this.sourceControllers.delete(previousSource);
1018
+ }
1019
+ }
1020
+ this.controllerSources.set(controllerName, normalizedSourceFile);
1021
+ if (!this.sourceControllers.has(normalizedSourceFile)) {
1022
+ this.sourceControllers.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1023
+ }
1024
+ this.sourceControllers.get(normalizedSourceFile)?.add(controllerName);
1025
+ }
1026
+ normalizeFilePath(filePath) {
1027
+ return path.resolve(filePath).replace(/\\/g, "/");
1028
+ }
940
1029
  }();
941
1030
 
942
1031
  var DecoratorType = /* @__PURE__ */ ((DecoratorType2) => {
@@ -1237,25 +1326,47 @@ class NattyScope {
1237
1326
  }
1238
1327
  }
1239
1328
 
1329
+ function normalizeFilePath$1(filePath) {
1330
+ return path.resolve(filePath).replace(/\\/g, "/");
1331
+ }
1240
1332
  class NattyContainer {
1241
1333
  constructor() {
1242
1334
  this.regs = /* @__PURE__ */ new Map();
1243
1335
  this.singletons = /* @__PURE__ */ new Map();
1336
+ this.tokenSources = /* @__PURE__ */ new Map();
1337
+ this.sourceTokens = /* @__PURE__ */ new Map();
1338
+ this.metadataKeySources = /* @__PURE__ */ new Map();
1339
+ this.sourceMetadataKeys = /* @__PURE__ */ new Map();
1244
1340
  }
1245
- register(token, desc) {
1341
+ register(token, desc, sourceFile) {
1246
1342
  this.regs.set(token, desc);
1343
+ if (sourceFile) {
1344
+ this.trackTokenSource(token, sourceFile);
1345
+ }
1247
1346
  }
1248
- addTransient(token, useClass, useFactory) {
1249
- this.register(token, { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory });
1347
+ addTransient(token, useClass, useFactory, sourceFile) {
1348
+ this.register(
1349
+ token,
1350
+ { lifetime: Lifetime.Transient, useClass: useClass ?? token, useFactory },
1351
+ sourceFile
1352
+ );
1250
1353
  }
1251
- addScoped(token, useClass, useFactory) {
1252
- this.register(token, { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory });
1354
+ addScoped(token, useClass, useFactory, sourceFile) {
1355
+ this.register(
1356
+ token,
1357
+ { lifetime: Lifetime.Scoped, useClass: useClass ?? token, useFactory },
1358
+ sourceFile
1359
+ );
1253
1360
  }
1254
- addSingleton(token, useClass, useFactory) {
1255
- this.register(token, { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory });
1361
+ addSingleton(token, useClass, useFactory, sourceFile) {
1362
+ this.register(
1363
+ token,
1364
+ { lifetime: Lifetime.Singleton, useClass: useClass ?? token, useFactory },
1365
+ sourceFile
1366
+ );
1256
1367
  }
1257
- addInstance(token, value) {
1258
- this.register(token, { lifetime: Lifetime.Singleton, useValue: value });
1368
+ addInstance(token, value, sourceFile) {
1369
+ this.register(token, { lifetime: Lifetime.Singleton, useValue: value }, sourceFile);
1259
1370
  this.singletons.set(token, value);
1260
1371
  }
1261
1372
  createScope() {
@@ -1300,6 +1411,57 @@ class NattyContainer {
1300
1411
  _getDescriptor(token) {
1301
1412
  return this.regs.get(token);
1302
1413
  }
1414
+ trackMetadataKey(sourceFile, key) {
1415
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1416
+ const previousSource = this.metadataKeySources.get(key);
1417
+ if (previousSource && previousSource !== normalizedSourceFile) {
1418
+ this.sourceMetadataKeys.get(previousSource)?.delete(key);
1419
+ if (this.sourceMetadataKeys.get(previousSource)?.size === 0) {
1420
+ this.sourceMetadataKeys.delete(previousSource);
1421
+ }
1422
+ }
1423
+ this.metadataKeySources.set(key, normalizedSourceFile);
1424
+ if (!this.sourceMetadataKeys.has(normalizedSourceFile)) {
1425
+ this.sourceMetadataKeys.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1426
+ }
1427
+ this.sourceMetadataKeys.get(normalizedSourceFile)?.add(key);
1428
+ }
1429
+ clearRegistrationsFromSource(sourceFile) {
1430
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1431
+ const tokens = Array.from(this.sourceTokens.get(normalizedSourceFile) || []);
1432
+ const metadataKeys = Array.from(
1433
+ this.sourceMetadataKeys.get(normalizedSourceFile) || []
1434
+ );
1435
+ for (const token of tokens) {
1436
+ this.regs.delete(token);
1437
+ this.singletons.delete(token);
1438
+ this.tokenSources.delete(token);
1439
+ }
1440
+ for (const metadataKey of metadataKeys) {
1441
+ this.metadataKeySources.delete(metadataKey);
1442
+ }
1443
+ this.sourceTokens.delete(normalizedSourceFile);
1444
+ this.sourceMetadataKeys.delete(normalizedSourceFile);
1445
+ return {
1446
+ metadataKeys,
1447
+ tokens
1448
+ };
1449
+ }
1450
+ trackTokenSource(token, sourceFile) {
1451
+ const normalizedSourceFile = normalizeFilePath$1(sourceFile);
1452
+ const previousSource = this.tokenSources.get(token);
1453
+ if (previousSource && previousSource !== normalizedSourceFile) {
1454
+ this.sourceTokens.get(previousSource)?.delete(token);
1455
+ if (this.sourceTokens.get(previousSource)?.size === 0) {
1456
+ this.sourceTokens.delete(previousSource);
1457
+ }
1458
+ }
1459
+ this.tokenSources.set(token, normalizedSourceFile);
1460
+ if (!this.sourceTokens.has(normalizedSourceFile)) {
1461
+ this.sourceTokens.set(normalizedSourceFile, /* @__PURE__ */ new Set());
1462
+ }
1463
+ this.sourceTokens.get(normalizedSourceFile)?.add(token);
1464
+ }
1303
1465
  }
1304
1466
 
1305
1467
  const nattyServiceResolver = new NattyContainer();
@@ -1412,7 +1574,7 @@ class Resolver extends RequestProcessor {
1412
1574
  });
1413
1575
  } else
1414
1576
  result = new HttpException({
1415
- body: getPreResponseBody({ message: ex.message, stack: ex.stack }),
1577
+ body: { message: ex.message, stack: ex.stack },
1416
1578
  status: HttpStatusCode.serverError
1417
1579
  }).getResponse();
1418
1580
  }
@@ -1447,7 +1609,10 @@ class RequestHandler extends Resolver {
1447
1609
  });
1448
1610
  } else
1449
1611
  return new HttpException({
1450
- body: ex,
1612
+ body: {
1613
+ message: ex?.message,
1614
+ stack: ex?.stack
1615
+ },
1451
1616
  status: HttpStatusCode.serverError
1452
1617
  });
1453
1618
  }
@@ -1460,20 +1625,59 @@ class HttpHandler {
1460
1625
  async processRequest(httpContext) {
1461
1626
  const requestProcessor = new RequestHandler(httpContext);
1462
1627
  const result = await requestProcessor.onRequest();
1463
- return result;
1628
+ if (result instanceof HttpResponse) {
1629
+ return result;
1630
+ }
1631
+ if (result instanceof HttpException) {
1632
+ return result.getResponse();
1633
+ }
1634
+ return new HttpResponse({ body: result });
1635
+ }
1636
+ }
1637
+
1638
+ const STACK_PATH_REGEX = /\bat (?:(?:.+?) \()?(.+):(\d+):(\d+)\)?$/;
1639
+ function normalizeFilePath(filePath) {
1640
+ return path.resolve(filePath).replace(/\\/g, "/");
1641
+ }
1642
+ function isInternalRegistrationFrame(filePath) {
1643
+ const normalizedPath = normalizeFilePath(filePath);
1644
+ 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/");
1645
+ }
1646
+ function getRegistrationSourceFile() {
1647
+ const stack = new Error().stack;
1648
+ return getRegistrationSourceFileFromStack(stack);
1649
+ }
1650
+ function getRegistrationSourceFileFromStack(stack) {
1651
+ if (!stack) {
1652
+ return void 0;
1464
1653
  }
1654
+ const lines = stack.split("\n").slice(1);
1655
+ for (const line of lines) {
1656
+ const match = line.trim().match(STACK_PATH_REGEX);
1657
+ if (!match) {
1658
+ continue;
1659
+ }
1660
+ const filePath = normalizeFilePath(match[1]);
1661
+ if (!isInternalRegistrationFrame(filePath)) {
1662
+ return filePath;
1663
+ }
1664
+ }
1665
+ return void 0;
1465
1666
  }
1466
1667
 
1467
1668
  function injectable(options = {}) {
1468
1669
  return (targetConstructor) => {
1469
1670
  const lt = options.lifetime ?? Lifetime.Transient;
1671
+ const sourceFile = getRegistrationSourceFile();
1470
1672
  if (lt === Lifetime.Singleton)
1471
- nattyServiceResolver.addSingleton(targetConstructor);
1673
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1472
1674
  else if (lt === Lifetime.Scoped)
1473
- nattyServiceResolver.addScoped(targetConstructor);
1675
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1474
1676
  else
1475
- nattyServiceResolver.addTransient(targetConstructor);
1677
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1476
1678
  commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1679
+ if (sourceFile)
1680
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
1477
1681
  };
1478
1682
  }
1479
1683
 
@@ -1759,7 +1963,12 @@ const Results = {
1759
1963
  };
1760
1964
 
1761
1965
  function base(params, type, additionaConfig) {
1762
- decoratorStateContainer.register(params, type, additionaConfig);
1966
+ decoratorStateContainer.register(
1967
+ params,
1968
+ type,
1969
+ additionaConfig,
1970
+ getRegistrationSourceFile()
1971
+ );
1763
1972
  }
1764
1973
 
1765
1974
  function useFilter(config) {
@@ -1784,6 +1993,12 @@ function authenticationOnly() {
1784
1993
  };
1785
1994
  }
1786
1995
 
1996
+ function onException(exceptionFilter) {
1997
+ return function(target, propertyKey, descriptor) {
1998
+ base({ target, propertyKey, descriptor }, DecoratorType.onException, exceptionFilter);
1999
+ };
2000
+ }
2001
+
1787
2002
  function setEnvInfo(envTsDefinition, envValueInfo) {
1788
2003
  if (envTsDefinition && envValueInfo) {
1789
2004
  commonContainer.setEnvTsDefinition(envTsDefinition);
@@ -1831,30 +2046,40 @@ function getTokenTypeName(token) {
1831
2046
  return key.slice(separatorIndex + 1);
1832
2047
  }
1833
2048
 
1834
- function registerLifetime(targetConstructor, lifetime) {
2049
+ function registerLifetime(targetConstructor, sourceFile, lifetime) {
1835
2050
  if (lifetime === Lifetime.Singleton) {
1836
- nattyServiceResolver.addSingleton(targetConstructor);
2051
+ nattyServiceResolver.addSingleton(targetConstructor, void 0, void 0, sourceFile);
1837
2052
  return;
1838
2053
  }
1839
2054
  if (lifetime === Lifetime.Scoped) {
1840
- nattyServiceResolver.addScoped(targetConstructor);
2055
+ nattyServiceResolver.addScoped(targetConstructor, void 0, void 0, sourceFile);
1841
2056
  return;
1842
2057
  }
1843
2058
  if (lifetime === Lifetime.Transient) {
1844
- nattyServiceResolver.addTransient(targetConstructor);
2059
+ nattyServiceResolver.addTransient(targetConstructor, void 0, void 0, sourceFile);
1845
2060
  }
1846
2061
  }
1847
- function registerAliasTypeName(token) {
2062
+ function registerAliasTypeName(token, sourceFile) {
1848
2063
  const typeName = getTokenTypeName(token);
1849
2064
  if (typeName) {
1850
2065
  commonContainer.setMetadata(typeName, token, "services");
2066
+ if (sourceFile)
2067
+ nattyServiceResolver.trackMetadataKey(sourceFile, typeName);
1851
2068
  }
1852
2069
  }
1853
2070
  function registerDiToken(targetConstructor, token, options = {}) {
2071
+ const sourceFile = getRegistrationSourceFile();
1854
2072
  commonContainer.setMetadata(targetConstructor.name, targetConstructor, "services");
1855
- registerAliasTypeName(token);
1856
- registerLifetime(targetConstructor, options.lifetime);
1857
- nattyServiceResolver.addTransient(token, void 0, (serviceProvider) => serviceProvider.get(targetConstructor));
2073
+ if (sourceFile)
2074
+ nattyServiceResolver.trackMetadataKey(sourceFile, targetConstructor.name);
2075
+ registerAliasTypeName(token, sourceFile);
2076
+ registerLifetime(targetConstructor, sourceFile, options.lifetime);
2077
+ nattyServiceResolver.addTransient(
2078
+ token,
2079
+ void 0,
2080
+ (serviceProvider) => serviceProvider.get(targetConstructor),
2081
+ sourceFile
2082
+ );
1858
2083
  }
1859
2084
 
1860
2085
  function di(token, options = {}) {
@@ -1867,4 +2092,22 @@ function createServiceScope() {
1867
2092
  return nattyServiceResolver.createScope();
1868
2093
  }
1869
2094
 
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 };
2095
+ function clearHotReloadRegistrations(files) {
2096
+ for (const filePath of files || []) {
2097
+ const result = nattyServiceResolver.clearRegistrationsFromSource(filePath);
2098
+ decoratorStateContainer.clearFromSource(filePath);
2099
+ for (const metadataKey of result.metadataKeys) {
2100
+ commonContainer.deleteMetadataValue(metadataKey, "services");
2101
+ }
2102
+ }
2103
+ }
2104
+
2105
+ function clearHotReloadControllerMetadata(controllerNames) {
2106
+ decoratorStateContainer.clearControllers(controllerNames || []);
2107
+ }
2108
+
2109
+ function replaceRoutes(manifest) {
2110
+ nattyContainer.replaceRoutes(manifest);
2111
+ }
2112
+
2113
+ 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.65",
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.65"
21
21
  },
22
22
  "devDependencies": {
23
23
  "unbuild": "1.2.1"