@noxfly/noxus 3.0.0-dev.2 → 3.0.0-dev.4

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/renderer.mjs CHANGED
@@ -4,131 +4,155 @@
4
4
  * @author NoxFly
5
5
  */
6
6
  var __defProp = Object.defineProperty;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
8
12
 
9
13
  // src/utils/forward-ref.ts
10
- var _ForwardReference = class _ForwardReference {
11
- constructor(forwardRefFn) {
12
- this.forwardRefFn = forwardRefFn;
14
+ var _ForwardReference, ForwardReference;
15
+ var init_forward_ref = __esm({
16
+ "src/utils/forward-ref.ts"() {
17
+ "use strict";
18
+ _ForwardReference = class _ForwardReference {
19
+ constructor(forwardRefFn) {
20
+ this.forwardRefFn = forwardRefFn;
21
+ }
22
+ };
23
+ __name(_ForwardReference, "ForwardReference");
24
+ ForwardReference = _ForwardReference;
13
25
  }
14
- };
15
- __name(_ForwardReference, "ForwardReference");
16
- var ForwardReference = _ForwardReference;
26
+ });
17
27
 
18
28
  // src/DI/token.ts
19
- var _Token = class _Token {
20
- constructor(target) {
21
- this.target = target;
22
- this.description = typeof target === "string" ? target : target.name;
23
- }
24
- toString() {
25
- return `Token(${this.description})`;
29
+ var _Token, Token;
30
+ var init_token = __esm({
31
+ "src/DI/token.ts"() {
32
+ "use strict";
33
+ _Token = class _Token {
34
+ constructor(target) {
35
+ this.target = target;
36
+ this.description = typeof target === "string" ? target : target.name;
37
+ }
38
+ toString() {
39
+ return `Token(${this.description})`;
40
+ }
41
+ };
42
+ __name(_Token, "Token");
43
+ Token = _Token;
26
44
  }
27
- };
28
- __name(_Token, "Token");
29
- var Token = _Token;
45
+ });
30
46
 
31
47
  // src/DI/app-injector.ts
32
48
  function keyOf(k) {
33
49
  return k;
34
50
  }
35
- __name(keyOf, "keyOf");
36
- var _AppInjector = class _AppInjector {
37
- constructor(name = null) {
38
- this.name = name;
39
- this.bindings = /* @__PURE__ */ new Map();
40
- this.singletons = /* @__PURE__ */ new Map();
41
- this.scoped = /* @__PURE__ */ new Map();
42
- }
43
- /**
44
- * Creates a child scope for per-request lifetime resolution.
45
- */
46
- createScope() {
47
- const scope = new _AppInjector();
48
- scope.bindings = this.bindings;
49
- scope.singletons = this.singletons;
50
- return scope;
51
- }
52
- /**
53
- * Registers a binding explicitly.
54
- */
55
- register(key, implementation, lifetime, deps = []) {
56
- const k = keyOf(key);
57
- if (!this.bindings.has(k)) {
58
- this.bindings.set(k, { lifetime, implementation, deps });
59
- }
60
- }
61
- /**
62
- * Resolves a dependency by token or class reference.
63
- */
64
- resolve(target) {
65
- if (target instanceof ForwardReference) {
66
- return this._resolveForwardRef(target);
67
- }
68
- const k = keyOf(target);
69
- if (this.singletons.has(k)) {
70
- return this.singletons.get(k);
71
- }
72
- const binding = this.bindings.get(k);
73
- if (!binding) {
74
- const name = target instanceof Token ? target.description : target.name ?? "unknown";
75
- throw new Error(
76
- `[Noxus DI] No binding found for "${name}".
77
- Did you forget to declare it in @Injectable({ deps }) or in bootstrapApplication({ singletons })?`
78
- );
79
- }
80
- switch (binding.lifetime) {
81
- case "transient":
82
- return this._instantiate(binding);
83
- case "scope": {
84
- if (this.scoped.has(k)) return this.scoped.get(k);
85
- const inst = this._instantiate(binding);
86
- this.scoped.set(k, inst);
87
- return inst;
51
+ var _AppInjector, AppInjector, RootInjector;
52
+ var init_app_injector = __esm({
53
+ "src/DI/app-injector.ts"() {
54
+ "use strict";
55
+ init_forward_ref();
56
+ init_token();
57
+ __name(keyOf, "keyOf");
58
+ _AppInjector = class _AppInjector {
59
+ constructor(name = null) {
60
+ this.name = name;
61
+ this.bindings = /* @__PURE__ */ new Map();
62
+ this.singletons = /* @__PURE__ */ new Map();
63
+ this.scoped = /* @__PURE__ */ new Map();
64
+ }
65
+ /**
66
+ * Creates a child scope for per-request lifetime resolution.
67
+ */
68
+ createScope() {
69
+ const scope = new _AppInjector();
70
+ scope.bindings = this.bindings;
71
+ scope.singletons = this.singletons;
72
+ return scope;
88
73
  }
89
- case "singleton": {
90
- if (this.singletons.has(k)) return this.singletons.get(k);
91
- const inst = this._instantiate(binding);
92
- this.singletons.set(k, inst);
93
- if (binding.instance === void 0) {
94
- binding.instance = inst;
74
+ /**
75
+ * Registers a binding explicitly.
76
+ */
77
+ register(key, implementation, lifetime, deps = []) {
78
+ const k = keyOf(key);
79
+ if (!this.bindings.has(k)) {
80
+ this.bindings.set(k, { lifetime, implementation, deps });
95
81
  }
96
- return inst;
97
82
  }
98
- }
99
- }
100
- // -------------------------------------------------------------------------
101
- _resolveForwardRef(ref) {
102
- return new Proxy({}, {
103
- get: /* @__PURE__ */ __name((_obj, prop, receiver) => {
104
- const realType = ref.forwardRefFn();
105
- const instance = this.resolve(realType);
106
- const value = Reflect.get(instance, prop, receiver);
107
- return typeof value === "function" ? value.bind(instance) : value;
108
- }, "get"),
109
- set: /* @__PURE__ */ __name((_obj, prop, value, receiver) => {
110
- const realType = ref.forwardRefFn();
111
- const instance = this.resolve(realType);
112
- return Reflect.set(instance, prop, value, receiver);
113
- }, "set"),
114
- getPrototypeOf: /* @__PURE__ */ __name(() => {
115
- const realType = ref.forwardRefFn();
116
- return realType.prototype;
117
- }, "getPrototypeOf")
118
- });
119
- }
120
- _instantiate(binding) {
121
- const resolvedDeps = binding.deps.map((dep) => this.resolve(dep));
122
- return new binding.implementation(...resolvedDeps);
83
+ /**
84
+ * Resolves a dependency by token or class reference.
85
+ */
86
+ resolve(target) {
87
+ if (target instanceof ForwardReference) {
88
+ return this._resolveForwardRef(target);
89
+ }
90
+ const k = keyOf(target);
91
+ if (this.singletons.has(k)) {
92
+ return this.singletons.get(k);
93
+ }
94
+ const binding = this.bindings.get(k);
95
+ if (!binding) {
96
+ const name = target instanceof Token ? target.description : target.name ?? "unknown";
97
+ throw new Error(
98
+ `[Noxus DI] No binding found for "${name}".
99
+ Did you forget to declare it in @Injectable({ deps }) or in bootstrapApplication({ singletons })?`
100
+ );
101
+ }
102
+ switch (binding.lifetime) {
103
+ case "transient":
104
+ return this._instantiate(binding);
105
+ case "scope": {
106
+ if (this.scoped.has(k)) return this.scoped.get(k);
107
+ const inst = this._instantiate(binding);
108
+ this.scoped.set(k, inst);
109
+ return inst;
110
+ }
111
+ case "singleton": {
112
+ if (this.singletons.has(k)) return this.singletons.get(k);
113
+ const inst = this._instantiate(binding);
114
+ this.singletons.set(k, inst);
115
+ if (binding.instance === void 0) {
116
+ binding.instance = inst;
117
+ }
118
+ return inst;
119
+ }
120
+ }
121
+ }
122
+ // -------------------------------------------------------------------------
123
+ _resolveForwardRef(ref) {
124
+ let resolved;
125
+ return new Proxy({}, {
126
+ get: /* @__PURE__ */ __name((_obj, prop, receiver) => {
127
+ resolved ?? (resolved = this.resolve(ref.forwardRefFn()));
128
+ const value = Reflect.get(resolved, prop, receiver);
129
+ return typeof value === "function" ? value.bind(resolved) : value;
130
+ }, "get"),
131
+ set: /* @__PURE__ */ __name((_obj, prop, value, receiver) => {
132
+ resolved ?? (resolved = this.resolve(ref.forwardRefFn()));
133
+ return Reflect.set(resolved, prop, value, receiver);
134
+ }, "set"),
135
+ getPrototypeOf: /* @__PURE__ */ __name(() => {
136
+ resolved ?? (resolved = this.resolve(ref.forwardRefFn()));
137
+ return Object.getPrototypeOf(resolved);
138
+ }, "getPrototypeOf")
139
+ });
140
+ }
141
+ _instantiate(binding) {
142
+ const resolvedDeps = binding.deps.map((dep) => this.resolve(dep));
143
+ return new binding.implementation(...resolvedDeps);
144
+ }
145
+ };
146
+ __name(_AppInjector, "AppInjector");
147
+ AppInjector = _AppInjector;
148
+ RootInjector = new AppInjector("root");
123
149
  }
124
- };
125
- __name(_AppInjector, "AppInjector");
126
- var AppInjector = _AppInjector;
127
- var RootInjector = new AppInjector("root");
150
+ });
128
151
 
129
152
  // src/internal/request.ts
153
+ init_app_injector();
130
154
  var _Request = class _Request {
131
- constructor(event, senderId, id, method, path, body) {
155
+ constructor(event, senderId, id, method, path, body, query) {
132
156
  this.event = event;
133
157
  this.senderId = senderId;
134
158
  this.id = id;
@@ -138,6 +162,7 @@ var _Request = class _Request {
138
162
  this.context = RootInjector.createScope();
139
163
  this.params = {};
140
164
  this.path = path.replace(/^\/|\/$/g, "");
165
+ this.query = query ?? {};
141
166
  }
142
167
  };
143
168
  __name(_Request, "Request");
@@ -340,6 +365,9 @@ var _NoxRendererClient = class _NoxRendererClient {
340
365
  console.error(`[Noxus] No pending handler found for request ${response.requestId}.`);
341
366
  return;
342
367
  }
368
+ if (pending.timer !== void 0) {
369
+ clearTimeout(pending.timer);
370
+ }
343
371
  this.pendingRequests.delete(response.requestId);
344
372
  this.onRequestCompleted(pending, response);
345
373
  if (response.status >= 400) {
@@ -353,6 +381,8 @@ var _NoxRendererClient = class _NoxRendererClient {
353
381
  this.bridge = resolvedBridge ?? null;
354
382
  this.initMessageType = options.initMessageType ?? DEFAULT_INIT_EVENT;
355
383
  this.generateRequestId = options.generateRequestId ?? defaultRequestId;
384
+ this.requestTimeout = options.requestTimeout ?? 1e4;
385
+ this.enableLogging = options.enableLogging ?? true;
356
386
  }
357
387
  async setup() {
358
388
  if (this.isReady) {
@@ -380,6 +410,11 @@ var _NoxRendererClient = class _NoxRendererClient {
380
410
  this.socketPort = void 0;
381
411
  this.senderId = void 0;
382
412
  this.isReady = false;
413
+ for (const pending of this.pendingRequests.values()) {
414
+ if (pending.timer !== void 0) {
415
+ clearTimeout(pending.timer);
416
+ }
417
+ }
383
418
  this.pendingRequests.clear();
384
419
  }
385
420
  async request(request) {
@@ -404,6 +439,12 @@ var _NoxRendererClient = class _NoxRendererClient {
404
439
  request: message,
405
440
  submittedAt: Date.now()
406
441
  };
442
+ if (this.requestTimeout > 0) {
443
+ pending.timer = setTimeout(() => {
444
+ this.pendingRequests.delete(message.requestId);
445
+ reject(this.createErrorResponse(message.requestId, `Request timed out after ${this.requestTimeout}ms`));
446
+ }, this.requestTimeout);
447
+ }
407
448
  this.pendingRequests.set(message.requestId, pending);
408
449
  this.requestPort.postMessage(message);
409
450
  });
@@ -421,6 +462,9 @@ var _NoxRendererClient = class _NoxRendererClient {
421
462
  return this.senderId;
422
463
  }
423
464
  onRequestCompleted(pending, response) {
465
+ if (!this.enableLogging) {
466
+ return;
467
+ }
424
468
  if (typeof console.groupCollapsed === "function") {
425
469
  console.groupCollapsed(`${response.status} ${pending.request.method} /${pending.request.path}`);
426
470
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noxfly/noxus",
3
- "version": "3.0.0-dev.2",
3
+ "version": "3.0.0-dev.4",
4
4
  "main": "dist/main.js",
5
5
  "module": "dist/main.mjs",
6
6
  "types": "dist/main.d.ts",
@@ -122,21 +122,20 @@ export class AppInjector {
122
122
  // -------------------------------------------------------------------------
123
123
 
124
124
  private _resolveForwardRef<T>(ref: ForwardReference<T>): T {
125
+ let resolved: T | undefined;
125
126
  return new Proxy({} as object, {
126
127
  get: (_obj, prop, receiver) => {
127
- const realType = ref.forwardRefFn();
128
- const instance = this.resolve(realType) as Record<string | symbol, unknown>;
129
- const value = Reflect.get(instance, prop, receiver);
130
- return typeof value === 'function' ? (value as Function).bind(instance) : value;
128
+ resolved ??= this.resolve(ref.forwardRefFn()) as T;
129
+ const value = Reflect.get(resolved as object, prop, receiver);
130
+ return typeof value === 'function' ? (value as Function).bind(resolved) : value;
131
131
  },
132
132
  set: (_obj, prop, value, receiver) => {
133
- const realType = ref.forwardRefFn();
134
- const instance = this.resolve(realType) as object;
135
- return Reflect.set(instance, prop, value, receiver);
133
+ resolved ??= this.resolve(ref.forwardRefFn()) as T;
134
+ return Reflect.set(resolved as object, prop, value, receiver);
136
135
  },
137
136
  getPrototypeOf: () => {
138
- const realType = ref.forwardRefFn();
139
- return (realType as unknown as { prototype: object }).prototype;
137
+ resolved ??= this.resolve(ref.forwardRefFn()) as T;
138
+ return Object.getPrototypeOf(resolved);
140
139
  },
141
140
  }) as T;
142
141
  }
@@ -152,6 +151,20 @@ export class AppInjector {
152
151
  */
153
152
  export const RootInjector = new AppInjector('root');
154
153
 
154
+ /**
155
+ * Resets the root injector to a clean state.
156
+ * **Intended for testing only** — clears all bindings, singletons, and scoped instances
157
+ * so that each test can start from a fresh DI container without restarting the process.
158
+ */
159
+ export function resetRootInjector(): void {
160
+ RootInjector.bindings.clear();
161
+ RootInjector.singletons.clear();
162
+ RootInjector.scoped.clear();
163
+ // Lazy import to avoid circular dependency (InjectorExplorer → app-injector → InjectorExplorer)
164
+ const { InjectorExplorer } = require('./injector-explorer') as typeof import('./injector-explorer');
165
+ InjectorExplorer.reset();
166
+ }
167
+
155
168
  /**
156
169
  * Convenience function: resolve a token from the root injector.
157
170
  */
@@ -8,7 +8,8 @@ import { Lifetime, RootInjector } from './app-injector';
8
8
  import { TokenKey } from './token';
9
9
  import { Type } from '../utils/types';
10
10
  import { Logger } from '../utils/logger';
11
- import { Guard, Middleware } from "src/main";
11
+ import { Guard } from '../decorators/guards.decorator';
12
+ import { Middleware } from '../decorators/middleware.decorator';
12
13
 
13
14
  export interface PendingRegistration {
14
15
  key: TokenKey;
@@ -19,6 +20,17 @@ export interface PendingRegistration {
19
20
  pathPrefix?: string;
20
21
  }
21
22
 
23
+ /**
24
+ * Callback invoked for each controller registration discovered during flush.
25
+ * Decouples InjectorExplorer from the Router to avoid circular imports.
26
+ */
27
+ export type ControllerRegistrar = (
28
+ controllerClass: Type<unknown>,
29
+ pathPrefix: string,
30
+ routeGuards: Guard[],
31
+ routeMiddlewares: Middleware[],
32
+ ) => void;
33
+
22
34
  /**
23
35
  * InjectorExplorer accumulates registrations emitted by decorators
24
36
  * at import time, then flushes them in two phases (bind → resolve)
@@ -31,11 +43,21 @@ export class InjectorExplorer {
31
43
  private static readonly pending: PendingRegistration[] = [];
32
44
  private static processed = false;
33
45
  private static accumulating = false;
46
+ private static loadingLock: Promise<void> = Promise.resolve();
47
+ private static controllerRegistrar: ControllerRegistrar | null = null;
34
48
 
35
49
  // -------------------------------------------------------------------------
36
50
  // Public API
37
51
  // -------------------------------------------------------------------------
38
52
 
53
+ /**
54
+ * Sets the callback used to register controllers.
55
+ * Must be called once before processPending (typically by bootstrapApplication).
56
+ */
57
+ public static setControllerRegistrar(registrar: ControllerRegistrar): void {
58
+ InjectorExplorer.controllerRegistrar = registrar;
59
+ }
60
+
39
61
  public static enqueue(reg: PendingRegistration): void {
40
62
  if (InjectorExplorer.processed && !InjectorExplorer.accumulating) {
41
63
  InjectorExplorer._registerImmediate(reg);
@@ -66,23 +88,47 @@ export class InjectorExplorer {
66
88
  /**
67
89
  * Exits accumulation mode and flushes queued registrations
68
90
  * with the same two-phase guarantee as processPending.
91
+ * Serialised through a lock to prevent concurrent lazy loads from corrupting the queue.
69
92
  */
70
93
  public static flushAccumulated(
71
94
  routeGuards: Guard[] = [],
72
95
  routeMiddlewares: Middleware[] = [],
73
96
  pathPrefix = '',
74
- ): void {
75
- InjectorExplorer.accumulating = false;
76
- const queue = [...InjectorExplorer.pending];
77
- InjectorExplorer.pending.length = 0;
78
- InjectorExplorer._phaseOne(queue);
97
+ ): Promise<void> {
98
+ InjectorExplorer.loadingLock = InjectorExplorer.loadingLock.then(() => {
99
+ InjectorExplorer.accumulating = false;
100
+ const queue = [...InjectorExplorer.pending];
101
+ InjectorExplorer.pending.length = 0;
102
+ InjectorExplorer._phaseOne(queue);
103
+
104
+ // Stamp the path prefix on controller registrations
105
+ for (const reg of queue) {
106
+ if (reg.isController) reg.pathPrefix = pathPrefix;
107
+ }
79
108
 
80
- // Stamp the path prefix on controller registrations
81
- for (const reg of queue) {
82
- if (reg.isController) reg.pathPrefix = pathPrefix;
83
- }
109
+ InjectorExplorer._phaseTwo(queue, undefined, routeGuards, routeMiddlewares);
110
+ });
84
111
 
85
- InjectorExplorer._phaseTwo(queue, undefined, routeGuards, routeMiddlewares);
112
+ return InjectorExplorer.loadingLock;
113
+ }
114
+
115
+ /**
116
+ * Returns a Promise that resolves once all pending flushAccumulated calls
117
+ * have completed. Useful for awaiting lazy-load serialisation.
118
+ */
119
+ public static waitForFlush(): Promise<void> {
120
+ return InjectorExplorer.loadingLock;
121
+ }
122
+
123
+ /**
124
+ * Resets the explorer state. Intended for tests only.
125
+ */
126
+ public static reset(): void {
127
+ InjectorExplorer.pending.length = 0;
128
+ InjectorExplorer.processed = false;
129
+ InjectorExplorer.accumulating = false;
130
+ InjectorExplorer.loadingLock = Promise.resolve();
131
+ InjectorExplorer.controllerRegistrar = null;
86
132
  }
87
133
 
88
134
  // -------------------------------------------------------------------------
@@ -96,13 +142,22 @@ export class InjectorExplorer {
96
142
  }
97
143
  }
98
144
 
99
- /** Phase 2: resolve singletons and register controllers in the router. */
145
+ /** Phase 2: validate deps, resolve singletons and register controllers via the registrar callback. */
100
146
  private static _phaseTwo(
101
147
  queue: PendingRegistration[],
102
148
  overrides?: Map<TokenKey, unknown>,
103
149
  routeGuards: Guard[] = [],
104
150
  routeMiddlewares: Middleware[] = [],
105
151
  ): void {
152
+ // Early dependency validation: warn about deps that have no binding
153
+ for (const reg of queue) {
154
+ for (const dep of reg.deps) {
155
+ if (!RootInjector.bindings.has(dep as any) && !RootInjector.singletons.has(dep as any)) {
156
+ Logger.warn(`[Noxus DI] "${reg.implementation.name}" declares dep "${(dep as any).name ?? dep}" which has no binding`);
157
+ }
158
+ }
159
+ }
160
+
106
161
  for (const reg of queue) {
107
162
  // Apply value overrides (e.g. singleton instances provided via bootstrapApplication config)
108
163
  if (overrides?.has(reg.key)) {
@@ -117,10 +172,15 @@ export class InjectorExplorer {
117
172
  }
118
173
 
119
174
  if (reg.isController) {
120
- // Lazily import Router to avoid circular dependency at module load time
121
- const { Router } = require('../internal/router') as { Router: { prototype: { registerController(t: Type<unknown>): void } } };
122
- const router = RootInjector.resolve(Router as any) as { registerController(t: Type<unknown>, pathPrefix: string, routeGuards: Guard[], routeMiddlewares: Middleware[]): void };
123
- router.registerController(reg.implementation, reg.pathPrefix ?? '', routeGuards, routeMiddlewares);
175
+ if (!InjectorExplorer.controllerRegistrar) {
176
+ throw new Error('[Noxus DI] No controller registrar set. Call InjectorExplorer.setControllerRegistrar() before processing.');
177
+ }
178
+ InjectorExplorer.controllerRegistrar(
179
+ reg.implementation,
180
+ reg.pathPrefix ?? '',
181
+ routeGuards,
182
+ routeMiddlewares,
183
+ );
124
184
  } else if (reg.lifetime !== 'singleton') {
125
185
  Logger.log(`Registered ${reg.implementation.name} as ${reg.lifetime}`);
126
186
  }
@@ -134,10 +194,8 @@ export class InjectorExplorer {
134
194
  RootInjector.resolve(reg.key);
135
195
  }
136
196
 
137
- if (reg.isController) {
138
- const { Router } = require('../internal/router') as { Router: { prototype: { registerController(t: Type<unknown>): void } } };
139
- const router = RootInjector.resolve(Router as any) as { registerController(t: Type<unknown>): void };
140
- router.registerController(reg.implementation);
197
+ if (reg.isController && InjectorExplorer.controllerRegistrar) {
198
+ InjectorExplorer.controllerRegistrar(reg.implementation, '', [], []);
141
199
  }
142
200
  }
143
201
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { app, BrowserWindow, ipcMain, MessageChannelMain } from 'electron/main';
8
- import { Guard } from "src/main";
8
+ import { Guard } from "../decorators/guards.decorator";
9
9
  import { Injectable } from '../decorators/injectable.decorator';
10
10
  import { Middleware } from '../decorators/middleware.decorator';
11
11
  import { inject } from '../DI/app-injector';
@@ -49,9 +49,11 @@ export interface IApp {
49
49
  export class NoxApp {
50
50
  private appService: IApp | undefined;
51
51
 
52
- private readonly router = inject(Router);
53
- private readonly socket = inject(NoxSocket);
54
- public readonly windowManager = inject(WindowManager);
52
+ constructor(
53
+ private readonly router: Router,
54
+ private readonly socket: NoxSocket,
55
+ public readonly windowManager: WindowManager,
56
+ ) {}
55
57
 
56
58
  // -------------------------------------------------------------------------
57
59
  // Initialisation
@@ -99,7 +101,7 @@ export class NoxApp {
99
101
  public async load(importFns: Array<() => Promise<unknown>>): Promise<this> {
100
102
  InjectorExplorer.beginAccumulate();
101
103
  await Promise.all(importFns.map((fn) => fn()));
102
- InjectorExplorer.flushAccumulated();
104
+ await InjectorExplorer.flushAccumulated();
103
105
  return this;
104
106
  }
105
107
 
@@ -134,7 +136,7 @@ export class NoxApp {
134
136
  // -------------------------------------------------------------------------
135
137
 
136
138
  private readonly onRendererMessage = async (event: Electron.MessageEvent): Promise<void> => {
137
- const { senderId, requestId, path, method, body }: import('./request').IRequest = event.data;
139
+ const { senderId, requestId, path, method, body, query }: import('./request').IRequest = event.data;
138
140
  const channels = this.socket.get(senderId);
139
141
 
140
142
  if (!channels) {
@@ -143,7 +145,7 @@ export class NoxApp {
143
145
  }
144
146
 
145
147
  try {
146
- const request = new Request(event, senderId, requestId, method, path, body);
148
+ const request = new Request(event, senderId, requestId, method, path, body, query);
147
149
  const response = await this.router.handle(request);
148
150
  channels.request.port1.postMessage(response);
149
151
  }
@@ -8,8 +8,10 @@ import { app } from 'electron/main';
8
8
  import { inject, RootInjector } from '../DI/app-injector';
9
9
  import { InjectorExplorer } from '../DI/injector-explorer';
10
10
  import { TokenKey } from '../DI/token';
11
+ import { Logger } from '../utils/logger';
11
12
  import { NoxApp } from './app';
12
13
  import { RouteDefinition } from "./routes";
14
+ import { Router } from './router';
13
15
 
14
16
  /**
15
17
  * A singleton value override: provides an already-constructed instance
@@ -68,6 +70,16 @@ export interface BootstrapConfig {
68
70
  * ]
69
71
  */
70
72
  eagerLoad?: Array<() => Promise<unknown>>;
73
+
74
+ /**
75
+ * Controls framework log verbosity.
76
+ * - `'debug'`: all messages (default during development).
77
+ * - `'info'`: info, warn, error, critical only.
78
+ * - `'none'`: completely silent — no framework logs.
79
+ *
80
+ * You can also pass an array of specific log levels to enable.
81
+ */
82
+ logLevel?: 'debug' | 'info' | 'none' | import('../utils/logger').LogLevel[];
71
83
  }
72
84
 
73
85
  /**
@@ -76,6 +88,17 @@ export interface BootstrapConfig {
76
88
  export async function bootstrapApplication(config: BootstrapConfig = {}): Promise<NoxApp> {
77
89
  await app.whenReady();
78
90
 
91
+ // Apply log level configuration
92
+ if (config.logLevel !== undefined) {
93
+ if (config.logLevel === 'none') {
94
+ Logger.setLogLevel([]);
95
+ } else if (Array.isArray(config.logLevel)) {
96
+ Logger.setLogLevel(config.logLevel);
97
+ } else {
98
+ Logger.setLogLevel(config.logLevel);
99
+ }
100
+ }
101
+
79
102
  // Build override map for the DI flush phase
80
103
  const overrides = new Map<TokenKey, unknown>();
81
104
 
@@ -86,6 +109,13 @@ export async function bootstrapApplication(config: BootstrapConfig = {}): Promis
86
109
  }
87
110
 
88
111
  // Flush all classes enqueued by decorators at import time (two-phase)
112
+ // Wire the controller registrar so InjectorExplorer can register controllers
113
+ // without directly importing Router (avoids circular dependency).
114
+ InjectorExplorer.setControllerRegistrar((controllerClass, pathPrefix, routeGuards, routeMiddlewares) => {
115
+ const router = inject(Router);
116
+ router.registerController(controllerClass, pathPrefix, routeGuards, routeMiddlewares);
117
+ });
118
+
89
119
  InjectorExplorer.processPending(overrides);
90
120
 
91
121
  // Resolve core framework singletons
@@ -94,7 +124,9 @@ export async function bootstrapApplication(config: BootstrapConfig = {}): Promis
94
124
  // Register routes from the routing table
95
125
  if (config.routes?.length) {
96
126
  for (const route of config.routes) {
97
- noxApp.lazy(route.path, route.load, route.guards, route.middlewares);
127
+ if (route.load) {
128
+ noxApp.lazy(route.path, route.load, route.guards, route.middlewares);
129
+ }
98
130
  }
99
131
  }
100
132