@noxfly/noxus 2.3.1 → 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 +62 -13
- package/dist/child.mjs +62 -13
- package/dist/main.d.mts +30 -3
- package/dist/main.d.ts +30 -3
- package/dist/main.js +82 -15
- package/dist/main.mjs +83 -16
- package/dist/renderer.d.mts +1 -1
- package/dist/renderer.d.ts +1 -1
- package/package.json +1 -1
- package/src/DI/injector-explorer.ts +75 -15
- package/src/app.ts +13 -2
- package/src/bootstrap.ts +37 -2
- package/src/decorators/injectable.decorator.ts +1 -1
- package/src/index.ts +1 -0
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
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
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
|
|
1338
|
-
|
|
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
|
|
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
|
|
1404
|
+
return;
|
|
1355
1405
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
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.
|
|
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
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
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
|
|
1269
|
-
|
|
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
|
|
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
|
|
1335
|
+
return;
|
|
1286
1336
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
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
|
|
833
|
-
if (RootInjector.bindings.has(target))
|
|
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
|
|
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
|
|
899
|
+
return;
|
|
850
900
|
}
|
|
851
|
-
|
|
852
|
-
|
|
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.
|
|
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
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
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
|
|
734
|
-
if (RootInjector.bindings.has(target))
|
|
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
|
|
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
|
|
800
|
+
return;
|
|
751
801
|
}
|
|
752
|
-
|
|
753
|
-
|
|
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.
|
|
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/dist/renderer.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { l as IBatchRequestItem, m as IBatchRequestPayload, n as IBatchResponsePayload, b as IPortRequester, p as IRendererEventMessage, k as IRequest, I as IResponse, N as NoxRendererClient, o as RENDERER_EVENT_TYPE, v as RendererClientOptions, s as RendererEventHandler, u as RendererEventRegistry, t as RendererEventSubscription, R as Request, q as createRendererEventMessage, r as isRendererEventMessage } from './index-DQBQQfMw.mjs';
|
|
1
|
+
export { e as AtomicHttpMethod, H as HttpMethod, l as IBatchRequestItem, m as IBatchRequestPayload, n as IBatchResponsePayload, b as IPortRequester, p as IRendererEventMessage, k as IRequest, I as IResponse, N as NoxRendererClient, o as RENDERER_EVENT_TYPE, v as RendererClientOptions, s as RendererEventHandler, u as RendererEventRegistry, t as RendererEventSubscription, R as Request, q as createRendererEventMessage, r as isRendererEventMessage } from './index-DQBQQfMw.mjs';
|
|
2
2
|
import './app-injector-B3MvgV3k.mjs';
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { l as IBatchRequestItem, m as IBatchRequestPayload, n as IBatchResponsePayload, b as IPortRequester, p as IRendererEventMessage, k as IRequest, I as IResponse, N as NoxRendererClient, o as RENDERER_EVENT_TYPE, v as RendererClientOptions, s as RendererEventHandler, u as RendererEventRegistry, t as RendererEventSubscription, R as Request, q as createRendererEventMessage, r as isRendererEventMessage } from './index-BxWQVi6C.js';
|
|
1
|
+
export { e as AtomicHttpMethod, H as HttpMethod, l as IBatchRequestItem, m as IBatchRequestPayload, n as IBatchResponsePayload, b as IPortRequester, p as IRendererEventMessage, k as IRequest, I as IResponse, N as NoxRendererClient, o as RENDERER_EVENT_TYPE, v as RendererClientOptions, s as RendererEventHandler, u as RendererEventRegistry, t as RendererEventSubscription, R as Request, q as createRendererEventMessage, r as isRendererEventMessage } from './index-BxWQVi6C.js';
|
|
2
2
|
import './app-injector-B3MvgV3k.js';
|
package/package.json
CHANGED
|
@@ -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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
26
|
-
if(
|
|
27
|
-
|
|
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
|
|
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
|
|
113
|
+
return;
|
|
49
114
|
}
|
|
50
115
|
|
|
51
|
-
|
|
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
|
|
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.
|
|
26
|
+
InjectorExplorer.enqueue(target as unknown as Type<any>, lifetime);
|
|
27
27
|
};
|
|
28
28
|
}
|
package/src/index.ts
CHANGED