@noxfly/noxus 2.3.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/child.js +173 -19
- package/dist/child.mjs +173 -19
- package/dist/main.d.mts +100 -22
- package/dist/main.d.ts +100 -22
- package/dist/main.js +236 -390
- package/dist/main.mjs +237 -388
- package/dist/renderer.d.mts +112 -1
- package/dist/renderer.d.ts +112 -1
- package/dist/renderer.js +40 -2
- package/dist/renderer.mjs +39 -2
- package/dist/{index-BxWQVi6C.d.ts → request-CdpZ9qZL.d.ts} +1 -87
- package/dist/{index-DQBQQfMw.d.mts → request-Dx_5Prte.d.mts} +1 -87
- package/package.json +1 -1
- package/src/DI/injector-explorer.ts +120 -15
- package/src/app.ts +50 -2
- package/src/bootstrap.ts +53 -3
- package/src/decorators/injectable.decorator.ts +1 -1
- package/src/index.ts +1 -0
- package/src/main.ts +0 -3
- package/src/router.ts +99 -6
package/dist/child.js
CHANGED
|
@@ -963,6 +963,7 @@ var _Router = class _Router {
|
|
|
963
963
|
constructor() {
|
|
964
964
|
__publicField(this, "routes", new RadixTree());
|
|
965
965
|
__publicField(this, "rootMiddlewares", []);
|
|
966
|
+
__publicField(this, "lazyRoutes", /* @__PURE__ */ new Map());
|
|
966
967
|
}
|
|
967
968
|
/**
|
|
968
969
|
* Registers a controller class with the router.
|
|
@@ -1010,6 +1011,24 @@ var _Router = class _Router {
|
|
|
1010
1011
|
Logger.log(`Mapped ${controllerClass.name}${controllerGuardsInfo} controller's routes`);
|
|
1011
1012
|
return this;
|
|
1012
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Registers a lazy route. The module behind this route prefix will only
|
|
1016
|
+
* be imported (and its controllers/services registered in DI) the first
|
|
1017
|
+
* time a request targets this prefix.
|
|
1018
|
+
*
|
|
1019
|
+
* @param pathPrefix - Route prefix (e.g. "auth"). Matched against the first segment of the request path.
|
|
1020
|
+
* @param loadModule - A function that returns a dynamic import promise.
|
|
1021
|
+
*/
|
|
1022
|
+
registerLazyRoute(pathPrefix, loadModule) {
|
|
1023
|
+
const normalized = pathPrefix.replace(/^\/+|\/+$/g, "");
|
|
1024
|
+
this.lazyRoutes.set(normalized, {
|
|
1025
|
+
loadModule,
|
|
1026
|
+
loading: null,
|
|
1027
|
+
loaded: false
|
|
1028
|
+
});
|
|
1029
|
+
Logger.log(`Registered lazy route prefix {${normalized}}`);
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1013
1032
|
/**
|
|
1014
1033
|
* Defines a middleware for the root of the application.
|
|
1015
1034
|
* This method allows you to register a middleware that will be applied to all requests
|
|
@@ -1042,7 +1061,7 @@ var _Router = class _Router {
|
|
|
1042
1061
|
};
|
|
1043
1062
|
let isCritical = false;
|
|
1044
1063
|
try {
|
|
1045
|
-
const routeDef = this.findRoute(request);
|
|
1064
|
+
const routeDef = await this.findRoute(request);
|
|
1046
1065
|
await this.resolveController(request, response, routeDef);
|
|
1047
1066
|
if (response.status > 400) {
|
|
1048
1067
|
throw new ResponseException(response.status, response.error);
|
|
@@ -1200,16 +1219,62 @@ var _Router = class _Router {
|
|
|
1200
1219
|
* @param request - The Request object containing the method and path to search for.
|
|
1201
1220
|
* @returns The IRouteDefinition for the matched route.
|
|
1202
1221
|
*/
|
|
1203
|
-
|
|
1222
|
+
/**
|
|
1223
|
+
* Attempts to find a route definition for the given request.
|
|
1224
|
+
* Returns undefined instead of throwing when the route is not found,
|
|
1225
|
+
* so the caller can try lazy-loading first.
|
|
1226
|
+
*/
|
|
1227
|
+
tryFindRoute(request) {
|
|
1204
1228
|
const matchedRoutes = this.routes.search(request.path);
|
|
1205
1229
|
if (matchedRoutes?.node === void 0 || matchedRoutes.node.children.length === 0) {
|
|
1206
|
-
|
|
1230
|
+
return void 0;
|
|
1207
1231
|
}
|
|
1208
1232
|
const routeDef = matchedRoutes.node.findExactChild(request.method);
|
|
1209
|
-
|
|
1210
|
-
|
|
1233
|
+
return routeDef?.value;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Finds the route definition for a given request.
|
|
1237
|
+
* If no eagerly-registered route matches, attempts to load a lazy module
|
|
1238
|
+
* whose prefix matches the request path, then retries.
|
|
1239
|
+
*/
|
|
1240
|
+
async findRoute(request) {
|
|
1241
|
+
const direct = this.tryFindRoute(request);
|
|
1242
|
+
if (direct) return direct;
|
|
1243
|
+
await this.tryLoadLazyRoute(request.path);
|
|
1244
|
+
const afterLazy = this.tryFindRoute(request);
|
|
1245
|
+
if (afterLazy) return afterLazy;
|
|
1246
|
+
throw new NotFoundException(`No route matches ${request.method} ${request.path}`);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Given a request path, checks whether a lazy route prefix matches
|
|
1250
|
+
* and triggers the dynamic import if it hasn't been loaded yet.
|
|
1251
|
+
*/
|
|
1252
|
+
async tryLoadLazyRoute(requestPath) {
|
|
1253
|
+
const firstSegment = requestPath.replace(/^\/+/, "").split("/")[0] ?? "";
|
|
1254
|
+
for (const [prefix, entry] of this.lazyRoutes) {
|
|
1255
|
+
if (entry.loaded) continue;
|
|
1256
|
+
const normalizedPath = requestPath.replace(/^\/+/, "");
|
|
1257
|
+
if (normalizedPath === prefix || normalizedPath.startsWith(prefix + "/") || firstSegment === prefix) {
|
|
1258
|
+
if (!entry.loading) {
|
|
1259
|
+
entry.loading = this.loadLazyModule(prefix, entry);
|
|
1260
|
+
}
|
|
1261
|
+
await entry.loading;
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1211
1264
|
}
|
|
1212
|
-
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Dynamically imports a lazy module and registers its decorated classes
|
|
1268
|
+
* (controllers, services) in the DI container using the two-phase strategy.
|
|
1269
|
+
*/
|
|
1270
|
+
async loadLazyModule(prefix, entry) {
|
|
1271
|
+
const t0 = performance.now();
|
|
1272
|
+
InjectorExplorer.beginAccumulate();
|
|
1273
|
+
await entry.loadModule();
|
|
1274
|
+
InjectorExplorer.flushAccumulated();
|
|
1275
|
+
entry.loaded = true;
|
|
1276
|
+
const t1 = performance.now();
|
|
1277
|
+
Logger.info(`Lazy-loaded module for prefix {${prefix}} in ${Math.round(t1 - t0)}ms`);
|
|
1213
1278
|
}
|
|
1214
1279
|
/**
|
|
1215
1280
|
* Resolves the controller for a given route definition.
|
|
@@ -1330,41 +1395,130 @@ Router = _ts_decorate([
|
|
|
1330
1395
|
// src/DI/injector-explorer.ts
|
|
1331
1396
|
var _InjectorExplorer = class _InjectorExplorer {
|
|
1332
1397
|
/**
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1398
|
+
* Enqueues a class for deferred registration.
|
|
1399
|
+
* Called by the @Injectable decorator at import time.
|
|
1400
|
+
*
|
|
1401
|
+
* If {@link processPending} has already been called (i.e. after bootstrap)
|
|
1402
|
+
* and accumulation mode is not active, the class is registered immediately
|
|
1403
|
+
* so that late dynamic imports (e.g. middlewares loaded after bootstrap)
|
|
1404
|
+
* work correctly.
|
|
1405
|
+
*
|
|
1406
|
+
* When accumulation mode is active (between {@link beginAccumulate} and
|
|
1407
|
+
* {@link flushAccumulated}), classes are queued instead — preserving the
|
|
1408
|
+
* two-phase binding/resolution guarantee for lazy-loaded modules.
|
|
1409
|
+
*/
|
|
1410
|
+
static enqueue(target, lifetime) {
|
|
1411
|
+
if (_InjectorExplorer.processed && !_InjectorExplorer.accumulating) {
|
|
1412
|
+
_InjectorExplorer.registerImmediate(target, lifetime);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
_InjectorExplorer.pending.push({
|
|
1416
|
+
target,
|
|
1417
|
+
lifetime
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Enters accumulation mode. While active, all decorated classes discovered
|
|
1422
|
+
* via dynamic imports are queued in {@link pending} rather than registered
|
|
1423
|
+
* immediately. Call {@link flushAccumulated} to process them with the
|
|
1424
|
+
* full two-phase (bind-then-resolve) guarantee.
|
|
1425
|
+
*/
|
|
1426
|
+
static beginAccumulate() {
|
|
1427
|
+
_InjectorExplorer.accumulating = true;
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Exits accumulation mode and processes every class queued since
|
|
1431
|
+
* {@link beginAccumulate} was called. Uses the same two-phase strategy
|
|
1432
|
+
* as {@link processPending} (register all bindings first, then resolve
|
|
1433
|
+
* singletons / controllers) so import ordering within a lazy batch
|
|
1434
|
+
* does not cause resolution failures.
|
|
1435
|
+
*/
|
|
1436
|
+
static flushAccumulated() {
|
|
1437
|
+
_InjectorExplorer.accumulating = false;
|
|
1438
|
+
const queue = [
|
|
1439
|
+
..._InjectorExplorer.pending
|
|
1440
|
+
];
|
|
1441
|
+
_InjectorExplorer.pending.length = 0;
|
|
1442
|
+
for (const { target, lifetime } of queue) {
|
|
1443
|
+
if (!RootInjector.bindings.has(target)) {
|
|
1444
|
+
RootInjector.bindings.set(target, {
|
|
1445
|
+
implementation: target,
|
|
1446
|
+
lifetime
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
for (const { target, lifetime } of queue) {
|
|
1451
|
+
_InjectorExplorer.processRegistration(target, lifetime);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Processes all pending registrations in two phases:
|
|
1456
|
+
* 1. Register all bindings (no instantiation) so every dependency is known.
|
|
1457
|
+
* 2. Resolve singletons, register controllers and log module readiness.
|
|
1458
|
+
*
|
|
1459
|
+
* This two-phase approach makes the system resilient to import ordering:
|
|
1460
|
+
* all bindings exist before any singleton is instantiated.
|
|
1461
|
+
*/
|
|
1462
|
+
static processPending() {
|
|
1463
|
+
const queue = _InjectorExplorer.pending;
|
|
1464
|
+
for (const { target, lifetime } of queue) {
|
|
1465
|
+
if (!RootInjector.bindings.has(target)) {
|
|
1466
|
+
RootInjector.bindings.set(target, {
|
|
1467
|
+
implementation: target,
|
|
1468
|
+
lifetime
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
for (const { target, lifetime } of queue) {
|
|
1473
|
+
_InjectorExplorer.processRegistration(target, lifetime);
|
|
1474
|
+
}
|
|
1475
|
+
queue.length = 0;
|
|
1476
|
+
_InjectorExplorer.processed = true;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Registers a single class immediately (post-bootstrap path).
|
|
1480
|
+
* Used for classes discovered via late dynamic imports.
|
|
1336
1481
|
*/
|
|
1337
|
-
static
|
|
1338
|
-
if (RootInjector.bindings.has(target))
|
|
1482
|
+
static registerImmediate(target, lifetime) {
|
|
1483
|
+
if (RootInjector.bindings.has(target)) {
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1339
1486
|
RootInjector.bindings.set(target, {
|
|
1340
1487
|
implementation: target,
|
|
1341
1488
|
lifetime
|
|
1342
1489
|
});
|
|
1490
|
+
_InjectorExplorer.processRegistration(target, lifetime);
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Performs phase-2 work for a single registration: resolve singletons,
|
|
1494
|
+
* register controllers, and log module readiness.
|
|
1495
|
+
*/
|
|
1496
|
+
static processRegistration(target, lifetime) {
|
|
1343
1497
|
if (lifetime === "singleton") {
|
|
1344
1498
|
RootInjector.resolve(target);
|
|
1345
1499
|
}
|
|
1346
1500
|
if (getModuleMetadata(target)) {
|
|
1347
1501
|
Logger.log(`${target.name} dependencies initialized`);
|
|
1348
|
-
return
|
|
1502
|
+
return;
|
|
1349
1503
|
}
|
|
1350
1504
|
const controllerMeta = getControllerMetadata(target);
|
|
1351
1505
|
if (controllerMeta) {
|
|
1352
1506
|
const router = RootInjector.resolve(Router);
|
|
1353
1507
|
router?.registerController(target);
|
|
1354
|
-
return
|
|
1508
|
+
return;
|
|
1355
1509
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
return RootInjector;
|
|
1510
|
+
if (getRouteMetadata(target).length > 0) {
|
|
1511
|
+
return;
|
|
1359
1512
|
}
|
|
1360
1513
|
if (getInjectableMetadata(target)) {
|
|
1361
1514
|
Logger.log(`Registered ${target.name} as ${lifetime}`);
|
|
1362
|
-
return RootInjector;
|
|
1363
1515
|
}
|
|
1364
|
-
return RootInjector;
|
|
1365
1516
|
}
|
|
1366
1517
|
};
|
|
1367
1518
|
__name(_InjectorExplorer, "InjectorExplorer");
|
|
1519
|
+
__publicField(_InjectorExplorer, "pending", []);
|
|
1520
|
+
__publicField(_InjectorExplorer, "processed", false);
|
|
1521
|
+
__publicField(_InjectorExplorer, "accumulating", false);
|
|
1368
1522
|
var InjectorExplorer = _InjectorExplorer;
|
|
1369
1523
|
|
|
1370
1524
|
// src/decorators/injectable.decorator.ts
|
|
@@ -1374,7 +1528,7 @@ function Injectable(lifetime = "scope") {
|
|
|
1374
1528
|
throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
|
|
1375
1529
|
}
|
|
1376
1530
|
defineInjectableMetadata(target, lifetime);
|
|
1377
|
-
InjectorExplorer.
|
|
1531
|
+
InjectorExplorer.enqueue(target, lifetime);
|
|
1378
1532
|
};
|
|
1379
1533
|
}
|
|
1380
1534
|
__name(Injectable, "Injectable");
|
package/dist/child.mjs
CHANGED
|
@@ -894,6 +894,7 @@ var _Router = class _Router {
|
|
|
894
894
|
constructor() {
|
|
895
895
|
__publicField(this, "routes", new RadixTree());
|
|
896
896
|
__publicField(this, "rootMiddlewares", []);
|
|
897
|
+
__publicField(this, "lazyRoutes", /* @__PURE__ */ new Map());
|
|
897
898
|
}
|
|
898
899
|
/**
|
|
899
900
|
* Registers a controller class with the router.
|
|
@@ -941,6 +942,24 @@ var _Router = class _Router {
|
|
|
941
942
|
Logger.log(`Mapped ${controllerClass.name}${controllerGuardsInfo} controller's routes`);
|
|
942
943
|
return this;
|
|
943
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Registers a lazy route. The module behind this route prefix will only
|
|
947
|
+
* be imported (and its controllers/services registered in DI) the first
|
|
948
|
+
* time a request targets this prefix.
|
|
949
|
+
*
|
|
950
|
+
* @param pathPrefix - Route prefix (e.g. "auth"). Matched against the first segment of the request path.
|
|
951
|
+
* @param loadModule - A function that returns a dynamic import promise.
|
|
952
|
+
*/
|
|
953
|
+
registerLazyRoute(pathPrefix, loadModule) {
|
|
954
|
+
const normalized = pathPrefix.replace(/^\/+|\/+$/g, "");
|
|
955
|
+
this.lazyRoutes.set(normalized, {
|
|
956
|
+
loadModule,
|
|
957
|
+
loading: null,
|
|
958
|
+
loaded: false
|
|
959
|
+
});
|
|
960
|
+
Logger.log(`Registered lazy route prefix {${normalized}}`);
|
|
961
|
+
return this;
|
|
962
|
+
}
|
|
944
963
|
/**
|
|
945
964
|
* Defines a middleware for the root of the application.
|
|
946
965
|
* This method allows you to register a middleware that will be applied to all requests
|
|
@@ -973,7 +992,7 @@ var _Router = class _Router {
|
|
|
973
992
|
};
|
|
974
993
|
let isCritical = false;
|
|
975
994
|
try {
|
|
976
|
-
const routeDef = this.findRoute(request);
|
|
995
|
+
const routeDef = await this.findRoute(request);
|
|
977
996
|
await this.resolveController(request, response, routeDef);
|
|
978
997
|
if (response.status > 400) {
|
|
979
998
|
throw new ResponseException(response.status, response.error);
|
|
@@ -1131,16 +1150,62 @@ var _Router = class _Router {
|
|
|
1131
1150
|
* @param request - The Request object containing the method and path to search for.
|
|
1132
1151
|
* @returns The IRouteDefinition for the matched route.
|
|
1133
1152
|
*/
|
|
1134
|
-
|
|
1153
|
+
/**
|
|
1154
|
+
* Attempts to find a route definition for the given request.
|
|
1155
|
+
* Returns undefined instead of throwing when the route is not found,
|
|
1156
|
+
* so the caller can try lazy-loading first.
|
|
1157
|
+
*/
|
|
1158
|
+
tryFindRoute(request) {
|
|
1135
1159
|
const matchedRoutes = this.routes.search(request.path);
|
|
1136
1160
|
if (matchedRoutes?.node === void 0 || matchedRoutes.node.children.length === 0) {
|
|
1137
|
-
|
|
1161
|
+
return void 0;
|
|
1138
1162
|
}
|
|
1139
1163
|
const routeDef = matchedRoutes.node.findExactChild(request.method);
|
|
1140
|
-
|
|
1141
|
-
|
|
1164
|
+
return routeDef?.value;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Finds the route definition for a given request.
|
|
1168
|
+
* If no eagerly-registered route matches, attempts to load a lazy module
|
|
1169
|
+
* whose prefix matches the request path, then retries.
|
|
1170
|
+
*/
|
|
1171
|
+
async findRoute(request) {
|
|
1172
|
+
const direct = this.tryFindRoute(request);
|
|
1173
|
+
if (direct) return direct;
|
|
1174
|
+
await this.tryLoadLazyRoute(request.path);
|
|
1175
|
+
const afterLazy = this.tryFindRoute(request);
|
|
1176
|
+
if (afterLazy) return afterLazy;
|
|
1177
|
+
throw new NotFoundException(`No route matches ${request.method} ${request.path}`);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Given a request path, checks whether a lazy route prefix matches
|
|
1181
|
+
* and triggers the dynamic import if it hasn't been loaded yet.
|
|
1182
|
+
*/
|
|
1183
|
+
async tryLoadLazyRoute(requestPath) {
|
|
1184
|
+
const firstSegment = requestPath.replace(/^\/+/, "").split("/")[0] ?? "";
|
|
1185
|
+
for (const [prefix, entry] of this.lazyRoutes) {
|
|
1186
|
+
if (entry.loaded) continue;
|
|
1187
|
+
const normalizedPath = requestPath.replace(/^\/+/, "");
|
|
1188
|
+
if (normalizedPath === prefix || normalizedPath.startsWith(prefix + "/") || firstSegment === prefix) {
|
|
1189
|
+
if (!entry.loading) {
|
|
1190
|
+
entry.loading = this.loadLazyModule(prefix, entry);
|
|
1191
|
+
}
|
|
1192
|
+
await entry.loading;
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1142
1195
|
}
|
|
1143
|
-
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Dynamically imports a lazy module and registers its decorated classes
|
|
1199
|
+
* (controllers, services) in the DI container using the two-phase strategy.
|
|
1200
|
+
*/
|
|
1201
|
+
async loadLazyModule(prefix, entry) {
|
|
1202
|
+
const t0 = performance.now();
|
|
1203
|
+
InjectorExplorer.beginAccumulate();
|
|
1204
|
+
await entry.loadModule();
|
|
1205
|
+
InjectorExplorer.flushAccumulated();
|
|
1206
|
+
entry.loaded = true;
|
|
1207
|
+
const t1 = performance.now();
|
|
1208
|
+
Logger.info(`Lazy-loaded module for prefix {${prefix}} in ${Math.round(t1 - t0)}ms`);
|
|
1144
1209
|
}
|
|
1145
1210
|
/**
|
|
1146
1211
|
* Resolves the controller for a given route definition.
|
|
@@ -1261,41 +1326,130 @@ Router = _ts_decorate([
|
|
|
1261
1326
|
// src/DI/injector-explorer.ts
|
|
1262
1327
|
var _InjectorExplorer = class _InjectorExplorer {
|
|
1263
1328
|
/**
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1329
|
+
* Enqueues a class for deferred registration.
|
|
1330
|
+
* Called by the @Injectable decorator at import time.
|
|
1331
|
+
*
|
|
1332
|
+
* If {@link processPending} has already been called (i.e. after bootstrap)
|
|
1333
|
+
* and accumulation mode is not active, the class is registered immediately
|
|
1334
|
+
* so that late dynamic imports (e.g. middlewares loaded after bootstrap)
|
|
1335
|
+
* work correctly.
|
|
1336
|
+
*
|
|
1337
|
+
* When accumulation mode is active (between {@link beginAccumulate} and
|
|
1338
|
+
* {@link flushAccumulated}), classes are queued instead — preserving the
|
|
1339
|
+
* two-phase binding/resolution guarantee for lazy-loaded modules.
|
|
1340
|
+
*/
|
|
1341
|
+
static enqueue(target, lifetime) {
|
|
1342
|
+
if (_InjectorExplorer.processed && !_InjectorExplorer.accumulating) {
|
|
1343
|
+
_InjectorExplorer.registerImmediate(target, lifetime);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
_InjectorExplorer.pending.push({
|
|
1347
|
+
target,
|
|
1348
|
+
lifetime
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Enters accumulation mode. While active, all decorated classes discovered
|
|
1353
|
+
* via dynamic imports are queued in {@link pending} rather than registered
|
|
1354
|
+
* immediately. Call {@link flushAccumulated} to process them with the
|
|
1355
|
+
* full two-phase (bind-then-resolve) guarantee.
|
|
1356
|
+
*/
|
|
1357
|
+
static beginAccumulate() {
|
|
1358
|
+
_InjectorExplorer.accumulating = true;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Exits accumulation mode and processes every class queued since
|
|
1362
|
+
* {@link beginAccumulate} was called. Uses the same two-phase strategy
|
|
1363
|
+
* as {@link processPending} (register all bindings first, then resolve
|
|
1364
|
+
* singletons / controllers) so import ordering within a lazy batch
|
|
1365
|
+
* does not cause resolution failures.
|
|
1366
|
+
*/
|
|
1367
|
+
static flushAccumulated() {
|
|
1368
|
+
_InjectorExplorer.accumulating = false;
|
|
1369
|
+
const queue = [
|
|
1370
|
+
..._InjectorExplorer.pending
|
|
1371
|
+
];
|
|
1372
|
+
_InjectorExplorer.pending.length = 0;
|
|
1373
|
+
for (const { target, lifetime } of queue) {
|
|
1374
|
+
if (!RootInjector.bindings.has(target)) {
|
|
1375
|
+
RootInjector.bindings.set(target, {
|
|
1376
|
+
implementation: target,
|
|
1377
|
+
lifetime
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
for (const { target, lifetime } of queue) {
|
|
1382
|
+
_InjectorExplorer.processRegistration(target, lifetime);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Processes all pending registrations in two phases:
|
|
1387
|
+
* 1. Register all bindings (no instantiation) so every dependency is known.
|
|
1388
|
+
* 2. Resolve singletons, register controllers and log module readiness.
|
|
1389
|
+
*
|
|
1390
|
+
* This two-phase approach makes the system resilient to import ordering:
|
|
1391
|
+
* all bindings exist before any singleton is instantiated.
|
|
1392
|
+
*/
|
|
1393
|
+
static processPending() {
|
|
1394
|
+
const queue = _InjectorExplorer.pending;
|
|
1395
|
+
for (const { target, lifetime } of queue) {
|
|
1396
|
+
if (!RootInjector.bindings.has(target)) {
|
|
1397
|
+
RootInjector.bindings.set(target, {
|
|
1398
|
+
implementation: target,
|
|
1399
|
+
lifetime
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
for (const { target, lifetime } of queue) {
|
|
1404
|
+
_InjectorExplorer.processRegistration(target, lifetime);
|
|
1405
|
+
}
|
|
1406
|
+
queue.length = 0;
|
|
1407
|
+
_InjectorExplorer.processed = true;
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Registers a single class immediately (post-bootstrap path).
|
|
1411
|
+
* Used for classes discovered via late dynamic imports.
|
|
1267
1412
|
*/
|
|
1268
|
-
static
|
|
1269
|
-
if (RootInjector.bindings.has(target))
|
|
1413
|
+
static registerImmediate(target, lifetime) {
|
|
1414
|
+
if (RootInjector.bindings.has(target)) {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1270
1417
|
RootInjector.bindings.set(target, {
|
|
1271
1418
|
implementation: target,
|
|
1272
1419
|
lifetime
|
|
1273
1420
|
});
|
|
1421
|
+
_InjectorExplorer.processRegistration(target, lifetime);
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Performs phase-2 work for a single registration: resolve singletons,
|
|
1425
|
+
* register controllers, and log module readiness.
|
|
1426
|
+
*/
|
|
1427
|
+
static processRegistration(target, lifetime) {
|
|
1274
1428
|
if (lifetime === "singleton") {
|
|
1275
1429
|
RootInjector.resolve(target);
|
|
1276
1430
|
}
|
|
1277
1431
|
if (getModuleMetadata(target)) {
|
|
1278
1432
|
Logger.log(`${target.name} dependencies initialized`);
|
|
1279
|
-
return
|
|
1433
|
+
return;
|
|
1280
1434
|
}
|
|
1281
1435
|
const controllerMeta = getControllerMetadata(target);
|
|
1282
1436
|
if (controllerMeta) {
|
|
1283
1437
|
const router = RootInjector.resolve(Router);
|
|
1284
1438
|
router?.registerController(target);
|
|
1285
|
-
return
|
|
1439
|
+
return;
|
|
1286
1440
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
return RootInjector;
|
|
1441
|
+
if (getRouteMetadata(target).length > 0) {
|
|
1442
|
+
return;
|
|
1290
1443
|
}
|
|
1291
1444
|
if (getInjectableMetadata(target)) {
|
|
1292
1445
|
Logger.log(`Registered ${target.name} as ${lifetime}`);
|
|
1293
|
-
return RootInjector;
|
|
1294
1446
|
}
|
|
1295
|
-
return RootInjector;
|
|
1296
1447
|
}
|
|
1297
1448
|
};
|
|
1298
1449
|
__name(_InjectorExplorer, "InjectorExplorer");
|
|
1450
|
+
__publicField(_InjectorExplorer, "pending", []);
|
|
1451
|
+
__publicField(_InjectorExplorer, "processed", false);
|
|
1452
|
+
__publicField(_InjectorExplorer, "accumulating", false);
|
|
1299
1453
|
var InjectorExplorer = _InjectorExplorer;
|
|
1300
1454
|
|
|
1301
1455
|
// src/decorators/injectable.decorator.ts
|
|
@@ -1305,7 +1459,7 @@ function Injectable(lifetime = "scope") {
|
|
|
1305
1459
|
throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
|
|
1306
1460
|
}
|
|
1307
1461
|
defineInjectableMetadata(target, lifetime);
|
|
1308
|
-
InjectorExplorer.
|
|
1462
|
+
InjectorExplorer.enqueue(target, lifetime);
|
|
1309
1463
|
};
|
|
1310
1464
|
}
|
|
1311
1465
|
__name(Injectable, "Injectable");
|