@noxfly/noxus 2.3.2 → 2.4.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 CHANGED
@@ -1330,41 +1330,90 @@ Router = _ts_decorate([
1330
1330
  // src/DI/injector-explorer.ts
1331
1331
  var _InjectorExplorer = class _InjectorExplorer {
1332
1332
  /**
1333
- * Registers the class as injectable.
1334
- * When a class is instantiated, if it has dependencies and those dependencies
1335
- * are listed using this method, they will be injected into the class constructor.
1333
+ * Enqueues a class for deferred registration.
1334
+ * Called by the @Injectable decorator at import time.
1335
+ *
1336
+ * If {@link processPending} has already been called (i.e. after bootstrap),
1337
+ * the class is registered immediately so that late dynamic imports
1338
+ * (e.g. middlewares loaded after bootstrap) work correctly.
1339
+ */
1340
+ static enqueue(target, lifetime) {
1341
+ if (_InjectorExplorer.processed) {
1342
+ _InjectorExplorer.registerImmediate(target, lifetime);
1343
+ return;
1344
+ }
1345
+ _InjectorExplorer.pending.push({
1346
+ target,
1347
+ lifetime
1348
+ });
1349
+ }
1350
+ /**
1351
+ * Processes all pending registrations in two phases:
1352
+ * 1. Register all bindings (no instantiation) so every dependency is known.
1353
+ * 2. Resolve singletons, register controllers and log module readiness.
1354
+ *
1355
+ * This two-phase approach makes the system resilient to import ordering:
1356
+ * all bindings exist before any singleton is instantiated.
1336
1357
  */
1337
- static register(target, lifetime) {
1338
- if (RootInjector.bindings.has(target)) return RootInjector;
1358
+ static processPending() {
1359
+ const queue = _InjectorExplorer.pending;
1360
+ for (const { target, lifetime } of queue) {
1361
+ if (!RootInjector.bindings.has(target)) {
1362
+ RootInjector.bindings.set(target, {
1363
+ implementation: target,
1364
+ lifetime
1365
+ });
1366
+ }
1367
+ }
1368
+ for (const { target, lifetime } of queue) {
1369
+ _InjectorExplorer.processRegistration(target, lifetime);
1370
+ }
1371
+ queue.length = 0;
1372
+ _InjectorExplorer.processed = true;
1373
+ }
1374
+ /**
1375
+ * Registers a single class immediately (post-bootstrap path).
1376
+ * Used for classes discovered via late dynamic imports.
1377
+ */
1378
+ static registerImmediate(target, lifetime) {
1379
+ if (RootInjector.bindings.has(target)) {
1380
+ return;
1381
+ }
1339
1382
  RootInjector.bindings.set(target, {
1340
1383
  implementation: target,
1341
1384
  lifetime
1342
1385
  });
1386
+ _InjectorExplorer.processRegistration(target, lifetime);
1387
+ }
1388
+ /**
1389
+ * Performs phase-2 work for a single registration: resolve singletons,
1390
+ * register controllers, and log module readiness.
1391
+ */
1392
+ static processRegistration(target, lifetime) {
1343
1393
  if (lifetime === "singleton") {
1344
1394
  RootInjector.resolve(target);
1345
1395
  }
1346
1396
  if (getModuleMetadata(target)) {
1347
1397
  Logger.log(`${target.name} dependencies initialized`);
1348
- return RootInjector;
1398
+ return;
1349
1399
  }
1350
1400
  const controllerMeta = getControllerMetadata(target);
1351
1401
  if (controllerMeta) {
1352
1402
  const router = RootInjector.resolve(Router);
1353
1403
  router?.registerController(target);
1354
- return RootInjector;
1404
+ return;
1355
1405
  }
1356
- const routeMeta = getRouteMetadata(target);
1357
- if (routeMeta) {
1358
- return RootInjector;
1406
+ if (getRouteMetadata(target).length > 0) {
1407
+ return;
1359
1408
  }
1360
1409
  if (getInjectableMetadata(target)) {
1361
1410
  Logger.log(`Registered ${target.name} as ${lifetime}`);
1362
- return RootInjector;
1363
1411
  }
1364
- return RootInjector;
1365
1412
  }
1366
1413
  };
1367
1414
  __name(_InjectorExplorer, "InjectorExplorer");
1415
+ __publicField(_InjectorExplorer, "pending", []);
1416
+ __publicField(_InjectorExplorer, "processed", false);
1368
1417
  var InjectorExplorer = _InjectorExplorer;
1369
1418
 
1370
1419
  // src/decorators/injectable.decorator.ts
@@ -1374,7 +1423,7 @@ function Injectable(lifetime = "scope") {
1374
1423
  throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
1375
1424
  }
1376
1425
  defineInjectableMetadata(target, lifetime);
1377
- InjectorExplorer.register(target, lifetime);
1426
+ InjectorExplorer.enqueue(target, lifetime);
1378
1427
  };
1379
1428
  }
1380
1429
  __name(Injectable, "Injectable");
package/dist/child.mjs CHANGED
@@ -1261,41 +1261,90 @@ Router = _ts_decorate([
1261
1261
  // src/DI/injector-explorer.ts
1262
1262
  var _InjectorExplorer = class _InjectorExplorer {
1263
1263
  /**
1264
- * Registers the class as injectable.
1265
- * When a class is instantiated, if it has dependencies and those dependencies
1266
- * are listed using this method, they will be injected into the class constructor.
1264
+ * Enqueues a class for deferred registration.
1265
+ * Called by the @Injectable decorator at import time.
1266
+ *
1267
+ * If {@link processPending} has already been called (i.e. after bootstrap),
1268
+ * the class is registered immediately so that late dynamic imports
1269
+ * (e.g. middlewares loaded after bootstrap) work correctly.
1270
+ */
1271
+ static enqueue(target, lifetime) {
1272
+ if (_InjectorExplorer.processed) {
1273
+ _InjectorExplorer.registerImmediate(target, lifetime);
1274
+ return;
1275
+ }
1276
+ _InjectorExplorer.pending.push({
1277
+ target,
1278
+ lifetime
1279
+ });
1280
+ }
1281
+ /**
1282
+ * Processes all pending registrations in two phases:
1283
+ * 1. Register all bindings (no instantiation) so every dependency is known.
1284
+ * 2. Resolve singletons, register controllers and log module readiness.
1285
+ *
1286
+ * This two-phase approach makes the system resilient to import ordering:
1287
+ * all bindings exist before any singleton is instantiated.
1267
1288
  */
1268
- static register(target, lifetime) {
1269
- if (RootInjector.bindings.has(target)) return RootInjector;
1289
+ static processPending() {
1290
+ const queue = _InjectorExplorer.pending;
1291
+ for (const { target, lifetime } of queue) {
1292
+ if (!RootInjector.bindings.has(target)) {
1293
+ RootInjector.bindings.set(target, {
1294
+ implementation: target,
1295
+ lifetime
1296
+ });
1297
+ }
1298
+ }
1299
+ for (const { target, lifetime } of queue) {
1300
+ _InjectorExplorer.processRegistration(target, lifetime);
1301
+ }
1302
+ queue.length = 0;
1303
+ _InjectorExplorer.processed = true;
1304
+ }
1305
+ /**
1306
+ * Registers a single class immediately (post-bootstrap path).
1307
+ * Used for classes discovered via late dynamic imports.
1308
+ */
1309
+ static registerImmediate(target, lifetime) {
1310
+ if (RootInjector.bindings.has(target)) {
1311
+ return;
1312
+ }
1270
1313
  RootInjector.bindings.set(target, {
1271
1314
  implementation: target,
1272
1315
  lifetime
1273
1316
  });
1317
+ _InjectorExplorer.processRegistration(target, lifetime);
1318
+ }
1319
+ /**
1320
+ * Performs phase-2 work for a single registration: resolve singletons,
1321
+ * register controllers, and log module readiness.
1322
+ */
1323
+ static processRegistration(target, lifetime) {
1274
1324
  if (lifetime === "singleton") {
1275
1325
  RootInjector.resolve(target);
1276
1326
  }
1277
1327
  if (getModuleMetadata(target)) {
1278
1328
  Logger.log(`${target.name} dependencies initialized`);
1279
- return RootInjector;
1329
+ return;
1280
1330
  }
1281
1331
  const controllerMeta = getControllerMetadata(target);
1282
1332
  if (controllerMeta) {
1283
1333
  const router = RootInjector.resolve(Router);
1284
1334
  router?.registerController(target);
1285
- return RootInjector;
1335
+ return;
1286
1336
  }
1287
- const routeMeta = getRouteMetadata(target);
1288
- if (routeMeta) {
1289
- return RootInjector;
1337
+ if (getRouteMetadata(target).length > 0) {
1338
+ return;
1290
1339
  }
1291
1340
  if (getInjectableMetadata(target)) {
1292
1341
  Logger.log(`Registered ${target.name} as ${lifetime}`);
1293
- return RootInjector;
1294
1342
  }
1295
- return RootInjector;
1296
1343
  }
1297
1344
  };
1298
1345
  __name(_InjectorExplorer, "InjectorExplorer");
1346
+ __publicField(_InjectorExplorer, "pending", []);
1347
+ __publicField(_InjectorExplorer, "processed", false);
1299
1348
  var InjectorExplorer = _InjectorExplorer;
1300
1349
 
1301
1350
  // src/decorators/injectable.decorator.ts
@@ -1305,7 +1354,7 @@ function Injectable(lifetime = "scope") {
1305
1354
  throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
1306
1355
  }
1307
1356
  defineInjectableMetadata(target, lifetime);
1308
- InjectorExplorer.register(target, lifetime);
1357
+ InjectorExplorer.enqueue(target, lifetime);
1309
1358
  };
1310
1359
  }
1311
1360
  __name(Injectable, "Injectable");
package/dist/main.d.mts CHANGED
@@ -2,6 +2,7 @@ import { M as MaybeAsync, T as Type } from './app-injector-B3MvgV3k.mjs';
2
2
  export { A as AppInjector, F as ForwardRefFn, a as ForwardReference, I as IBinding, L as Lifetime, R as RootInjector, f as forwardRef, i as inject } from './app-injector-B3MvgV3k.mjs';
3
3
  import { R as Request, I as IResponse, a as IGuard, b as IPortRequester } from './index-DQBQQfMw.mjs';
4
4
  export { e as AtomicHttpMethod, A as Authorize, D as Delete, G as Get, H as HttpMethod, l as IBatchRequestItem, m as IBatchRequestPayload, n as IBatchResponsePayload, p as IRendererEventMessage, k as IRequest, d as IRouteMetadata, N as NoxRendererClient, i as Patch, P as Post, h as Put, o as RENDERER_EVENT_TYPE, j as ROUTE_METADATA_KEY, v as RendererClientOptions, s as RendererEventHandler, u as RendererEventRegistry, t as RendererEventSubscription, q as createRendererEventMessage, g as getGuardForController, c as getGuardForControllerAction, f as getRouteMetadata, r as isRendererEventMessage } from './index-DQBQQfMw.mjs';
5
+ import { BrowserWindow } from 'electron/main';
5
6
  export { BadGatewayException, BadRequestException, ConflictException, ForbiddenException, GatewayTimeoutException, HttpVersionNotSupportedException, INJECTABLE_METADATA_KEY, INJECT_METADATA_KEY, Inject, Injectable, InsufficientStorageException, InternalServerException, LogLevel, Logger, LoopDetectedException, MethodNotAllowedException, NetworkAuthenticationRequiredException, NetworkConnectTimeoutException, NotAcceptableException, NotExtendedException, NotFoundException, NotImplementedException, PaymentRequiredException, RequestTimeoutException, ResponseException, ServiceUnavailableException, TooManyRequestsException, UnauthorizedException, UpgradeRequiredException, VariantAlsoNegotiatesException, getInjectableMetadata, hasInjectableMetadata } from './child.mjs';
6
7
 
7
8
  /**
@@ -185,7 +186,7 @@ declare class NoxSocket {
185
186
  */
186
187
  interface IApp {
187
188
  dispose(): Promise<void>;
188
- onReady(): Promise<void>;
189
+ onReady(mainWindow?: BrowserWindow): Promise<void>;
189
190
  onActivated(): Promise<void>;
190
191
  }
191
192
  /**
@@ -196,6 +197,7 @@ declare class NoxApp {
196
197
  private readonly router;
197
198
  private readonly socket;
198
199
  private app;
200
+ private mainWindow;
199
201
  /**
200
202
  *
201
203
  */
@@ -231,6 +233,12 @@ declare class NoxApp {
231
233
  * This method is called when all windows are closed, and it cleans up the message channels
232
234
  */
233
235
  private onAllWindowsClosed;
236
+ /**
237
+ * Sets the main BrowserWindow that was created early by bootstrapApplication.
238
+ * This window will be passed to IApp.onReady when start() is called.
239
+ * @param window - The BrowserWindow created during bootstrap.
240
+ */
241
+ setMainWindow(window: BrowserWindow): void;
234
242
  /**
235
243
  * Configures the NoxApp instance with the provided application class.
236
244
  * This method allows you to set the application class that will handle lifecycle events.
@@ -247,21 +255,40 @@ declare class NoxApp {
247
255
  use(middleware: Type<IMiddleware>): NoxApp;
248
256
  /**
249
257
  * Should be called after the bootstrapApplication function is called.
258
+ * Passes the early-created BrowserWindow (if any) to the configured IApp service.
250
259
  * @returns NoxApp instance for method chaining.
251
260
  */
252
261
  start(): NoxApp;
253
262
  }
254
263
 
255
264
 
265
+ /**
266
+ * Options for bootstrapping the Noxus application.
267
+ */
268
+ interface BootstrapOptions {
269
+ /**
270
+ * If provided, Noxus creates a BrowserWindow immediately after Electron is ready,
271
+ * before any DI processing occurs. This window is passed to the configured
272
+ * IApp service via onReady(). It allows the user to see a window as fast as possible,
273
+ * even before the application is fully initialized.
274
+ */
275
+ window?: Electron.BrowserWindowConstructorOptions;
276
+ }
256
277
  /**
257
278
  * Bootstraps the Noxus application.
258
279
  * This function initializes the application by creating an instance of NoxApp,
259
280
  * registering the root module, and starting the application.
281
+ *
282
+ * When {@link BootstrapOptions.window} is provided, a BrowserWindow is created
283
+ * immediately after Electron readiness — before DI resolution — so the user
284
+ * sees a window as quickly as possible.
285
+ *
260
286
  * @param rootModule - The root module of the application, decorated with @Module.
287
+ * @param options - Optional bootstrap configuration.
261
288
  * @return A promise that resolves to the NoxApp instance.
262
289
  * @throws Error if the root module is not decorated with @Module, or if the electron process could not start.
263
290
  */
264
- declare function bootstrapApplication(rootModule: Type<any>): Promise<NoxApp>;
291
+ declare function bootstrapApplication(rootModule: Type<any>, options?: BootstrapOptions): Promise<NoxApp>;
265
292
 
266
293
 
267
294
  /**
@@ -321,4 +348,4 @@ interface NoxusPreloadOptions {
321
348
  */
322
349
  declare function exposeNoxusBridge(options?: NoxusPreloadOptions): NoxusPreloadAPI;
323
350
 
324
- export { CONTROLLER_METADATA_KEY, Controller, type ControllerAction, type IApp, type IControllerMetadata, IGuard, type IMiddleware, type IModuleMetadata, IPortRequester, IResponse, type IRouteDefinition, MODULE_METADATA_KEY, MaybeAsync, Module, type NextFunction, NoxApp, NoxSocket, type NoxusPreloadAPI, type NoxusPreloadOptions, Request, Router, Type, UseMiddlewares, bootstrapApplication, exposeNoxusBridge, getControllerMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata };
351
+ export { type BootstrapOptions, CONTROLLER_METADATA_KEY, Controller, type ControllerAction, type IApp, type IControllerMetadata, IGuard, type IMiddleware, type IModuleMetadata, IPortRequester, IResponse, type IRouteDefinition, MODULE_METADATA_KEY, MaybeAsync, Module, type NextFunction, NoxApp, NoxSocket, type NoxusPreloadAPI, type NoxusPreloadOptions, Request, Router, Type, UseMiddlewares, bootstrapApplication, exposeNoxusBridge, getControllerMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata };
package/dist/main.d.ts CHANGED
@@ -2,6 +2,7 @@ import { M as MaybeAsync, T as Type } from './app-injector-B3MvgV3k.js';
2
2
  export { A as AppInjector, F as ForwardRefFn, a as ForwardReference, I as IBinding, L as Lifetime, R as RootInjector, f as forwardRef, i as inject } from './app-injector-B3MvgV3k.js';
3
3
  import { R as Request, I as IResponse, a as IGuard, b as IPortRequester } from './index-BxWQVi6C.js';
4
4
  export { e as AtomicHttpMethod, A as Authorize, D as Delete, G as Get, H as HttpMethod, l as IBatchRequestItem, m as IBatchRequestPayload, n as IBatchResponsePayload, p as IRendererEventMessage, k as IRequest, d as IRouteMetadata, N as NoxRendererClient, i as Patch, P as Post, h as Put, o as RENDERER_EVENT_TYPE, j as ROUTE_METADATA_KEY, v as RendererClientOptions, s as RendererEventHandler, u as RendererEventRegistry, t as RendererEventSubscription, q as createRendererEventMessage, g as getGuardForController, c as getGuardForControllerAction, f as getRouteMetadata, r as isRendererEventMessage } from './index-BxWQVi6C.js';
5
+ import { BrowserWindow } from 'electron/main';
5
6
  export { BadGatewayException, BadRequestException, ConflictException, ForbiddenException, GatewayTimeoutException, HttpVersionNotSupportedException, INJECTABLE_METADATA_KEY, INJECT_METADATA_KEY, Inject, Injectable, InsufficientStorageException, InternalServerException, LogLevel, Logger, LoopDetectedException, MethodNotAllowedException, NetworkAuthenticationRequiredException, NetworkConnectTimeoutException, NotAcceptableException, NotExtendedException, NotFoundException, NotImplementedException, PaymentRequiredException, RequestTimeoutException, ResponseException, ServiceUnavailableException, TooManyRequestsException, UnauthorizedException, UpgradeRequiredException, VariantAlsoNegotiatesException, getInjectableMetadata, hasInjectableMetadata } from './child.js';
6
7
 
7
8
  /**
@@ -185,7 +186,7 @@ declare class NoxSocket {
185
186
  */
186
187
  interface IApp {
187
188
  dispose(): Promise<void>;
188
- onReady(): Promise<void>;
189
+ onReady(mainWindow?: BrowserWindow): Promise<void>;
189
190
  onActivated(): Promise<void>;
190
191
  }
191
192
  /**
@@ -196,6 +197,7 @@ declare class NoxApp {
196
197
  private readonly router;
197
198
  private readonly socket;
198
199
  private app;
200
+ private mainWindow;
199
201
  /**
200
202
  *
201
203
  */
@@ -231,6 +233,12 @@ declare class NoxApp {
231
233
  * This method is called when all windows are closed, and it cleans up the message channels
232
234
  */
233
235
  private onAllWindowsClosed;
236
+ /**
237
+ * Sets the main BrowserWindow that was created early by bootstrapApplication.
238
+ * This window will be passed to IApp.onReady when start() is called.
239
+ * @param window - The BrowserWindow created during bootstrap.
240
+ */
241
+ setMainWindow(window: BrowserWindow): void;
234
242
  /**
235
243
  * Configures the NoxApp instance with the provided application class.
236
244
  * This method allows you to set the application class that will handle lifecycle events.
@@ -247,21 +255,40 @@ declare class NoxApp {
247
255
  use(middleware: Type<IMiddleware>): NoxApp;
248
256
  /**
249
257
  * Should be called after the bootstrapApplication function is called.
258
+ * Passes the early-created BrowserWindow (if any) to the configured IApp service.
250
259
  * @returns NoxApp instance for method chaining.
251
260
  */
252
261
  start(): NoxApp;
253
262
  }
254
263
 
255
264
 
265
+ /**
266
+ * Options for bootstrapping the Noxus application.
267
+ */
268
+ interface BootstrapOptions {
269
+ /**
270
+ * If provided, Noxus creates a BrowserWindow immediately after Electron is ready,
271
+ * before any DI processing occurs. This window is passed to the configured
272
+ * IApp service via onReady(). It allows the user to see a window as fast as possible,
273
+ * even before the application is fully initialized.
274
+ */
275
+ window?: Electron.BrowserWindowConstructorOptions;
276
+ }
256
277
  /**
257
278
  * Bootstraps the Noxus application.
258
279
  * This function initializes the application by creating an instance of NoxApp,
259
280
  * registering the root module, and starting the application.
281
+ *
282
+ * When {@link BootstrapOptions.window} is provided, a BrowserWindow is created
283
+ * immediately after Electron readiness — before DI resolution — so the user
284
+ * sees a window as quickly as possible.
285
+ *
260
286
  * @param rootModule - The root module of the application, decorated with @Module.
287
+ * @param options - Optional bootstrap configuration.
261
288
  * @return A promise that resolves to the NoxApp instance.
262
289
  * @throws Error if the root module is not decorated with @Module, or if the electron process could not start.
263
290
  */
264
- declare function bootstrapApplication(rootModule: Type<any>): Promise<NoxApp>;
291
+ declare function bootstrapApplication(rootModule: Type<any>, options?: BootstrapOptions): Promise<NoxApp>;
265
292
 
266
293
 
267
294
  /**
@@ -321,4 +348,4 @@ interface NoxusPreloadOptions {
321
348
  */
322
349
  declare function exposeNoxusBridge(options?: NoxusPreloadOptions): NoxusPreloadAPI;
323
350
 
324
- export { CONTROLLER_METADATA_KEY, Controller, type ControllerAction, type IApp, type IControllerMetadata, IGuard, type IMiddleware, type IModuleMetadata, IPortRequester, IResponse, type IRouteDefinition, MODULE_METADATA_KEY, MaybeAsync, Module, type NextFunction, NoxApp, NoxSocket, type NoxusPreloadAPI, type NoxusPreloadOptions, Request, Router, Type, UseMiddlewares, bootstrapApplication, exposeNoxusBridge, getControllerMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata };
351
+ export { type BootstrapOptions, CONTROLLER_METADATA_KEY, Controller, type ControllerAction, type IApp, type IControllerMetadata, IGuard, type IMiddleware, type IModuleMetadata, IPortRequester, IResponse, type IRouteDefinition, MODULE_METADATA_KEY, MaybeAsync, Module, type NextFunction, NoxApp, NoxSocket, type NoxusPreloadAPI, type NoxusPreloadOptions, Request, Router, Type, UseMiddlewares, bootstrapApplication, exposeNoxusBridge, getControllerMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata };
package/dist/main.js CHANGED
@@ -825,41 +825,90 @@ var Logger;
825
825
  // src/DI/injector-explorer.ts
826
826
  var _InjectorExplorer = class _InjectorExplorer {
827
827
  /**
828
- * Registers the class as injectable.
829
- * When a class is instantiated, if it has dependencies and those dependencies
830
- * are listed using this method, they will be injected into the class constructor.
828
+ * Enqueues a class for deferred registration.
829
+ * Called by the @Injectable decorator at import time.
830
+ *
831
+ * If {@link processPending} has already been called (i.e. after bootstrap),
832
+ * the class is registered immediately so that late dynamic imports
833
+ * (e.g. middlewares loaded after bootstrap) work correctly.
834
+ */
835
+ static enqueue(target, lifetime) {
836
+ if (_InjectorExplorer.processed) {
837
+ _InjectorExplorer.registerImmediate(target, lifetime);
838
+ return;
839
+ }
840
+ _InjectorExplorer.pending.push({
841
+ target,
842
+ lifetime
843
+ });
844
+ }
845
+ /**
846
+ * Processes all pending registrations in two phases:
847
+ * 1. Register all bindings (no instantiation) so every dependency is known.
848
+ * 2. Resolve singletons, register controllers and log module readiness.
849
+ *
850
+ * This two-phase approach makes the system resilient to import ordering:
851
+ * all bindings exist before any singleton is instantiated.
852
+ */
853
+ static processPending() {
854
+ const queue = _InjectorExplorer.pending;
855
+ for (const { target, lifetime } of queue) {
856
+ if (!RootInjector.bindings.has(target)) {
857
+ RootInjector.bindings.set(target, {
858
+ implementation: target,
859
+ lifetime
860
+ });
861
+ }
862
+ }
863
+ for (const { target, lifetime } of queue) {
864
+ _InjectorExplorer.processRegistration(target, lifetime);
865
+ }
866
+ queue.length = 0;
867
+ _InjectorExplorer.processed = true;
868
+ }
869
+ /**
870
+ * Registers a single class immediately (post-bootstrap path).
871
+ * Used for classes discovered via late dynamic imports.
831
872
  */
832
- static register(target, lifetime) {
833
- if (RootInjector.bindings.has(target)) return RootInjector;
873
+ static registerImmediate(target, lifetime) {
874
+ if (RootInjector.bindings.has(target)) {
875
+ return;
876
+ }
834
877
  RootInjector.bindings.set(target, {
835
878
  implementation: target,
836
879
  lifetime
837
880
  });
881
+ _InjectorExplorer.processRegistration(target, lifetime);
882
+ }
883
+ /**
884
+ * Performs phase-2 work for a single registration: resolve singletons,
885
+ * register controllers, and log module readiness.
886
+ */
887
+ static processRegistration(target, lifetime) {
838
888
  if (lifetime === "singleton") {
839
889
  RootInjector.resolve(target);
840
890
  }
841
891
  if (getModuleMetadata(target)) {
842
892
  Logger.log(`${target.name} dependencies initialized`);
843
- return RootInjector;
893
+ return;
844
894
  }
845
895
  const controllerMeta = getControllerMetadata(target);
846
896
  if (controllerMeta) {
847
897
  const router = RootInjector.resolve(Router);
848
898
  router?.registerController(target);
849
- return RootInjector;
899
+ return;
850
900
  }
851
- const routeMeta = getRouteMetadata(target);
852
- if (routeMeta) {
853
- return RootInjector;
901
+ if (getRouteMetadata(target).length > 0) {
902
+ return;
854
903
  }
855
904
  if (getInjectableMetadata(target)) {
856
905
  Logger.log(`Registered ${target.name} as ${lifetime}`);
857
- return RootInjector;
858
906
  }
859
- return RootInjector;
860
907
  }
861
908
  };
862
909
  __name(_InjectorExplorer, "InjectorExplorer");
910
+ __publicField(_InjectorExplorer, "pending", []);
911
+ __publicField(_InjectorExplorer, "processed", false);
863
912
  var InjectorExplorer = _InjectorExplorer;
864
913
 
865
914
  // src/decorators/injectable.decorator.ts
@@ -869,7 +918,7 @@ function Injectable(lifetime = "scope") {
869
918
  throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
870
919
  }
871
920
  defineInjectableMetadata(target, lifetime);
872
- InjectorExplorer.register(target, lifetime);
921
+ InjectorExplorer.enqueue(target, lifetime);
873
922
  };
874
923
  }
875
924
  __name(Injectable, "Injectable");
@@ -1591,6 +1640,7 @@ var _NoxApp = class _NoxApp {
1591
1640
  __publicField(this, "router");
1592
1641
  __publicField(this, "socket");
1593
1642
  __publicField(this, "app");
1643
+ __publicField(this, "mainWindow");
1594
1644
  /**
1595
1645
  *
1596
1646
  */
@@ -1697,6 +1747,14 @@ var _NoxApp = class _NoxApp {
1697
1747
  }
1698
1748
  }
1699
1749
  // ---
1750
+ /**
1751
+ * Sets the main BrowserWindow that was created early by bootstrapApplication.
1752
+ * This window will be passed to IApp.onReady when start() is called.
1753
+ * @param window - The BrowserWindow created during bootstrap.
1754
+ */
1755
+ setMainWindow(window2) {
1756
+ this.mainWindow = window2;
1757
+ }
1700
1758
  /**
1701
1759
  * Configures the NoxApp instance with the provided application class.
1702
1760
  * This method allows you to set the application class that will handle lifecycle events.
@@ -1719,10 +1777,11 @@ var _NoxApp = class _NoxApp {
1719
1777
  }
1720
1778
  /**
1721
1779
  * Should be called after the bootstrapApplication function is called.
1780
+ * Passes the early-created BrowserWindow (if any) to the configured IApp service.
1722
1781
  * @returns NoxApp instance for method chaining.
1723
1782
  */
1724
1783
  start() {
1725
- this.app?.onReady();
1784
+ this.app?.onReady(this.mainWindow);
1726
1785
  return this;
1727
1786
  }
1728
1787
  };
@@ -1739,12 +1798,20 @@ NoxApp = _ts_decorate3([
1739
1798
 
1740
1799
  // src/bootstrap.ts
1741
1800
  var import_main2 = require("electron/main");
1742
- async function bootstrapApplication(rootModule) {
1801
+ async function bootstrapApplication(rootModule, options) {
1743
1802
  if (!getModuleMetadata(rootModule)) {
1744
1803
  throw new Error(`Root module must be decorated with @Module`);
1745
1804
  }
1746
1805
  await import_main2.app.whenReady();
1806
+ let mainWindow;
1807
+ if (options?.window) {
1808
+ mainWindow = new import_main2.BrowserWindow(options.window);
1809
+ }
1810
+ InjectorExplorer.processPending();
1747
1811
  const noxApp = inject(NoxApp);
1812
+ if (mainWindow) {
1813
+ noxApp.setMainWindow(mainWindow);
1814
+ }
1748
1815
  await noxApp.init();
1749
1816
  return noxApp;
1750
1817
  }
package/dist/main.mjs CHANGED
@@ -726,41 +726,90 @@ var Logger;
726
726
  // src/DI/injector-explorer.ts
727
727
  var _InjectorExplorer = class _InjectorExplorer {
728
728
  /**
729
- * Registers the class as injectable.
730
- * When a class is instantiated, if it has dependencies and those dependencies
731
- * are listed using this method, they will be injected into the class constructor.
729
+ * Enqueues a class for deferred registration.
730
+ * Called by the @Injectable decorator at import time.
731
+ *
732
+ * If {@link processPending} has already been called (i.e. after bootstrap),
733
+ * the class is registered immediately so that late dynamic imports
734
+ * (e.g. middlewares loaded after bootstrap) work correctly.
735
+ */
736
+ static enqueue(target, lifetime) {
737
+ if (_InjectorExplorer.processed) {
738
+ _InjectorExplorer.registerImmediate(target, lifetime);
739
+ return;
740
+ }
741
+ _InjectorExplorer.pending.push({
742
+ target,
743
+ lifetime
744
+ });
745
+ }
746
+ /**
747
+ * Processes all pending registrations in two phases:
748
+ * 1. Register all bindings (no instantiation) so every dependency is known.
749
+ * 2. Resolve singletons, register controllers and log module readiness.
750
+ *
751
+ * This two-phase approach makes the system resilient to import ordering:
752
+ * all bindings exist before any singleton is instantiated.
753
+ */
754
+ static processPending() {
755
+ const queue = _InjectorExplorer.pending;
756
+ for (const { target, lifetime } of queue) {
757
+ if (!RootInjector.bindings.has(target)) {
758
+ RootInjector.bindings.set(target, {
759
+ implementation: target,
760
+ lifetime
761
+ });
762
+ }
763
+ }
764
+ for (const { target, lifetime } of queue) {
765
+ _InjectorExplorer.processRegistration(target, lifetime);
766
+ }
767
+ queue.length = 0;
768
+ _InjectorExplorer.processed = true;
769
+ }
770
+ /**
771
+ * Registers a single class immediately (post-bootstrap path).
772
+ * Used for classes discovered via late dynamic imports.
732
773
  */
733
- static register(target, lifetime) {
734
- if (RootInjector.bindings.has(target)) return RootInjector;
774
+ static registerImmediate(target, lifetime) {
775
+ if (RootInjector.bindings.has(target)) {
776
+ return;
777
+ }
735
778
  RootInjector.bindings.set(target, {
736
779
  implementation: target,
737
780
  lifetime
738
781
  });
782
+ _InjectorExplorer.processRegistration(target, lifetime);
783
+ }
784
+ /**
785
+ * Performs phase-2 work for a single registration: resolve singletons,
786
+ * register controllers, and log module readiness.
787
+ */
788
+ static processRegistration(target, lifetime) {
739
789
  if (lifetime === "singleton") {
740
790
  RootInjector.resolve(target);
741
791
  }
742
792
  if (getModuleMetadata(target)) {
743
793
  Logger.log(`${target.name} dependencies initialized`);
744
- return RootInjector;
794
+ return;
745
795
  }
746
796
  const controllerMeta = getControllerMetadata(target);
747
797
  if (controllerMeta) {
748
798
  const router = RootInjector.resolve(Router);
749
799
  router?.registerController(target);
750
- return RootInjector;
800
+ return;
751
801
  }
752
- const routeMeta = getRouteMetadata(target);
753
- if (routeMeta) {
754
- return RootInjector;
802
+ if (getRouteMetadata(target).length > 0) {
803
+ return;
755
804
  }
756
805
  if (getInjectableMetadata(target)) {
757
806
  Logger.log(`Registered ${target.name} as ${lifetime}`);
758
- return RootInjector;
759
807
  }
760
- return RootInjector;
761
808
  }
762
809
  };
763
810
  __name(_InjectorExplorer, "InjectorExplorer");
811
+ __publicField(_InjectorExplorer, "pending", []);
812
+ __publicField(_InjectorExplorer, "processed", false);
764
813
  var InjectorExplorer = _InjectorExplorer;
765
814
 
766
815
  // src/decorators/injectable.decorator.ts
@@ -770,7 +819,7 @@ function Injectable(lifetime = "scope") {
770
819
  throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
771
820
  }
772
821
  defineInjectableMetadata(target, lifetime);
773
- InjectorExplorer.register(target, lifetime);
822
+ InjectorExplorer.enqueue(target, lifetime);
774
823
  };
775
824
  }
776
825
  __name(Injectable, "Injectable");
@@ -1492,6 +1541,7 @@ var _NoxApp = class _NoxApp {
1492
1541
  __publicField(this, "router");
1493
1542
  __publicField(this, "socket");
1494
1543
  __publicField(this, "app");
1544
+ __publicField(this, "mainWindow");
1495
1545
  /**
1496
1546
  *
1497
1547
  */
@@ -1598,6 +1648,14 @@ var _NoxApp = class _NoxApp {
1598
1648
  }
1599
1649
  }
1600
1650
  // ---
1651
+ /**
1652
+ * Sets the main BrowserWindow that was created early by bootstrapApplication.
1653
+ * This window will be passed to IApp.onReady when start() is called.
1654
+ * @param window - The BrowserWindow created during bootstrap.
1655
+ */
1656
+ setMainWindow(window2) {
1657
+ this.mainWindow = window2;
1658
+ }
1601
1659
  /**
1602
1660
  * Configures the NoxApp instance with the provided application class.
1603
1661
  * This method allows you to set the application class that will handle lifecycle events.
@@ -1620,10 +1678,11 @@ var _NoxApp = class _NoxApp {
1620
1678
  }
1621
1679
  /**
1622
1680
  * Should be called after the bootstrapApplication function is called.
1681
+ * Passes the early-created BrowserWindow (if any) to the configured IApp service.
1623
1682
  * @returns NoxApp instance for method chaining.
1624
1683
  */
1625
1684
  start() {
1626
- this.app?.onReady();
1685
+ this.app?.onReady(this.mainWindow);
1627
1686
  return this;
1628
1687
  }
1629
1688
  };
@@ -1639,13 +1698,21 @@ NoxApp = _ts_decorate3([
1639
1698
  ], NoxApp);
1640
1699
 
1641
1700
  // src/bootstrap.ts
1642
- import { app as app2 } from "electron/main";
1643
- async function bootstrapApplication(rootModule) {
1701
+ import { app as app2, BrowserWindow as BrowserWindow2 } from "electron/main";
1702
+ async function bootstrapApplication(rootModule, options) {
1644
1703
  if (!getModuleMetadata(rootModule)) {
1645
1704
  throw new Error(`Root module must be decorated with @Module`);
1646
1705
  }
1647
1706
  await app2.whenReady();
1707
+ let mainWindow;
1708
+ if (options?.window) {
1709
+ mainWindow = new BrowserWindow2(options.window);
1710
+ }
1711
+ InjectorExplorer.processPending();
1648
1712
  const noxApp = inject(NoxApp);
1713
+ if (mainWindow) {
1714
+ noxApp.setMainWindow(mainWindow);
1715
+ }
1649
1716
  await noxApp.init();
1650
1717
  return noxApp;
1651
1718
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noxfly/noxus",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "main": "dist/main.js",
5
5
  "module": "dist/main.mjs",
6
6
  "types": "dist/main.d.ts",
@@ -13,31 +13,96 @@ import { Router } from "src/router";
13
13
  import { Logger } from "src/utils/logger";
14
14
  import { Type } from "src/utils/types";
15
15
 
16
+ interface PendingRegistration {
17
+ target: Type<unknown>;
18
+ lifetime: Lifetime;
19
+ }
20
+
16
21
  /**
17
22
  * InjectorExplorer is a utility class that explores the dependency injection system at the startup.
23
+ * It collects decorated classes during the import phase and defers their actual registration
24
+ * and resolution to when {@link processPending} is called by bootstrapApplication.
18
25
  */
19
26
  export class InjectorExplorer {
27
+ private static readonly pending: PendingRegistration[] = [];
28
+ private static processed = false;
29
+
20
30
  /**
21
- * Registers the class as injectable.
22
- * When a class is instantiated, if it has dependencies and those dependencies
23
- * are listed using this method, they will be injected into the class constructor.
31
+ * Enqueues a class for deferred registration.
32
+ * Called by the @Injectable decorator at import time.
33
+ *
34
+ * If {@link processPending} has already been called (i.e. after bootstrap),
35
+ * the class is registered immediately so that late dynamic imports
36
+ * (e.g. middlewares loaded after bootstrap) work correctly.
24
37
  */
25
- public static register(target: Type<unknown>, lifetime: Lifetime): typeof RootInjector {
26
- if(RootInjector.bindings.has(target)) // already registered
27
- return RootInjector;
38
+ public static enqueue(target: Type<unknown>, lifetime: Lifetime): void {
39
+ if(InjectorExplorer.processed) {
40
+ InjectorExplorer.registerImmediate(target, lifetime);
41
+ return;
42
+ }
43
+
44
+ InjectorExplorer.pending.push({ target, lifetime });
45
+ }
46
+
47
+ /**
48
+ * Processes all pending registrations in two phases:
49
+ * 1. Register all bindings (no instantiation) so every dependency is known.
50
+ * 2. Resolve singletons, register controllers and log module readiness.
51
+ *
52
+ * This two-phase approach makes the system resilient to import ordering:
53
+ * all bindings exist before any singleton is instantiated.
54
+ */
55
+ public static processPending(): void {
56
+ const queue = InjectorExplorer.pending;
57
+
58
+ // Phase 1: register all bindings without instantiation
59
+ for(const { target, lifetime } of queue) {
60
+ if(!RootInjector.bindings.has(target)) {
61
+ RootInjector.bindings.set(target, {
62
+ implementation: target,
63
+ lifetime
64
+ });
65
+ }
66
+ }
67
+
68
+ // Phase 2: resolve singletons, register controllers, log modules
69
+ for(const { target, lifetime } of queue) {
70
+ InjectorExplorer.processRegistration(target, lifetime);
71
+ }
72
+
73
+ queue.length = 0;
74
+ InjectorExplorer.processed = true;
75
+ }
76
+
77
+ /**
78
+ * Registers a single class immediately (post-bootstrap path).
79
+ * Used for classes discovered via late dynamic imports.
80
+ */
81
+ private static registerImmediate(target: Type<unknown>, lifetime: Lifetime): void {
82
+ if(RootInjector.bindings.has(target)) {
83
+ return;
84
+ }
28
85
 
29
86
  RootInjector.bindings.set(target, {
30
87
  implementation: target,
31
88
  lifetime
32
89
  });
33
90
 
91
+ InjectorExplorer.processRegistration(target, lifetime);
92
+ }
93
+
94
+ /**
95
+ * Performs phase-2 work for a single registration: resolve singletons,
96
+ * register controllers, and log module readiness.
97
+ */
98
+ private static processRegistration(target: Type<unknown>, lifetime: Lifetime): void {
34
99
  if(lifetime === 'singleton') {
35
100
  RootInjector.resolve(target);
36
101
  }
37
102
 
38
103
  if(getModuleMetadata(target)) {
39
104
  Logger.log(`${target.name} dependencies initialized`);
40
- return RootInjector;
105
+ return;
41
106
  }
42
107
 
43
108
  const controllerMeta = getControllerMetadata(target);
@@ -45,20 +110,15 @@ export class InjectorExplorer {
45
110
  if(controllerMeta) {
46
111
  const router = RootInjector.resolve(Router);
47
112
  router?.registerController(target);
48
- return RootInjector;
113
+ return;
49
114
  }
50
115
 
51
- const routeMeta = getRouteMetadata(target);
52
-
53
- if(routeMeta) {
54
- return RootInjector;
116
+ if(getRouteMetadata(target).length > 0) {
117
+ return;
55
118
  }
56
119
 
57
120
  if(getInjectableMetadata(target)) {
58
121
  Logger.log(`Registered ${target.name} as ${lifetime}`);
59
- return RootInjector;
60
122
  }
61
-
62
- return RootInjector;
63
123
  }
64
124
  }
package/src/app.ts CHANGED
@@ -21,7 +21,7 @@ import { Type } from "src/utils/types";
21
21
  */
22
22
  export interface IApp {
23
23
  dispose(): Promise<void>;
24
- onReady(): Promise<void>;
24
+ onReady(mainWindow?: BrowserWindow): Promise<void>;
25
25
  onActivated(): Promise<void>;
26
26
  }
27
27
 
@@ -32,6 +32,7 @@ export interface IApp {
32
32
  @Injectable('singleton')
33
33
  export class NoxApp {
34
34
  private app: IApp | undefined;
35
+ private mainWindow: BrowserWindow | undefined;
35
36
 
36
37
  /**
37
38
  *
@@ -163,6 +164,15 @@ export class NoxApp {
163
164
 
164
165
  // ---
165
166
 
167
+ /**
168
+ * Sets the main BrowserWindow that was created early by bootstrapApplication.
169
+ * This window will be passed to IApp.onReady when start() is called.
170
+ * @param window - The BrowserWindow created during bootstrap.
171
+ */
172
+ public setMainWindow(window: BrowserWindow): void {
173
+ this.mainWindow = window;
174
+ }
175
+
166
176
  /**
167
177
  * Configures the NoxApp instance with the provided application class.
168
178
  * This method allows you to set the application class that will handle lifecycle events.
@@ -187,10 +197,11 @@ export class NoxApp {
187
197
 
188
198
  /**
189
199
  * Should be called after the bootstrapApplication function is called.
200
+ * Passes the early-created BrowserWindow (if any) to the configured IApp service.
190
201
  * @returns NoxApp instance for method chaining.
191
202
  */
192
203
  public start(): NoxApp {
193
- this.app?.onReady();
204
+ this.app?.onReady(this.mainWindow);
194
205
  return this;
195
206
  }
196
207
  }
package/src/bootstrap.ts CHANGED
@@ -4,29 +4,64 @@
4
4
  * @author NoxFly
5
5
  */
6
6
 
7
- import { app } from "electron/main";
7
+ import { app, BrowserWindow } from "electron/main";
8
8
  import { NoxApp } from "src/app";
9
9
  import { getModuleMetadata } from "src/decorators/module.decorator";
10
10
  import { inject } from "src/DI/app-injector";
11
+ import { InjectorExplorer } from "src/DI/injector-explorer";
11
12
  import { Type } from "src/utils/types";
12
13
 
14
+ /**
15
+ * Options for bootstrapping the Noxus application.
16
+ */
17
+ export interface BootstrapOptions {
18
+ /**
19
+ * If provided, Noxus creates a BrowserWindow immediately after Electron is ready,
20
+ * before any DI processing occurs. This window is passed to the configured
21
+ * IApp service via onReady(). It allows the user to see a window as fast as possible,
22
+ * even before the application is fully initialized.
23
+ */
24
+ window?: Electron.BrowserWindowConstructorOptions;
25
+ }
26
+
13
27
  /**
14
28
  * Bootstraps the Noxus application.
15
29
  * This function initializes the application by creating an instance of NoxApp,
16
30
  * registering the root module, and starting the application.
31
+ *
32
+ * When {@link BootstrapOptions.window} is provided, a BrowserWindow is created
33
+ * immediately after Electron readiness — before DI resolution — so the user
34
+ * sees a window as quickly as possible.
35
+ *
17
36
  * @param rootModule - The root module of the application, decorated with @Module.
37
+ * @param options - Optional bootstrap configuration.
18
38
  * @return A promise that resolves to the NoxApp instance.
19
39
  * @throws Error if the root module is not decorated with @Module, or if the electron process could not start.
20
40
  */
21
- export async function bootstrapApplication(rootModule: Type<any>): Promise<NoxApp> {
41
+ export async function bootstrapApplication(rootModule: Type<any>, options?: BootstrapOptions): Promise<NoxApp> {
22
42
  if(!getModuleMetadata(rootModule)) {
23
43
  throw new Error(`Root module must be decorated with @Module`);
24
44
  }
25
45
 
26
46
  await app.whenReady();
27
47
 
48
+ // Create window immediately after Electron is ready, before DI processing.
49
+ // This gets pixels on screen as fast as possible.
50
+ let mainWindow: BrowserWindow | undefined;
51
+
52
+ if(options?.window) {
53
+ mainWindow = new BrowserWindow(options.window);
54
+ }
55
+
56
+ // Process all deferred injectable registrations (two-phase: bindings then resolution)
57
+ InjectorExplorer.processPending();
58
+
28
59
  const noxApp = inject(NoxApp);
29
60
 
61
+ if(mainWindow) {
62
+ noxApp.setMainWindow(mainWindow);
63
+ }
64
+
30
65
  await noxApp.init();
31
66
 
32
67
  return noxApp;
@@ -23,6 +23,6 @@ export function Injectable(lifetime: Lifetime = "scope"): ClassDecorator {
23
23
  throw new Error(`@Injectable can only be used on classes, not on ${typeof target}`);
24
24
  }
25
25
  defineInjectableMetadata(target, lifetime);
26
- InjectorExplorer.register(target as unknown as Type<any>, lifetime);
26
+ InjectorExplorer.enqueue(target as unknown as Type<any>, lifetime);
27
27
  };
28
28
  }