@noxfly/noxus 3.0.0-dev.3 → 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/.github/copilot-instructions.md +110 -14
- package/AGENTS.md +5 -0
- package/README.md +114 -7
- package/dist/child.d.mts +7 -1
- package/dist/child.d.ts +7 -1
- package/dist/child.js +402 -862
- package/dist/child.mjs +389 -850
- package/dist/main.d.mts +171 -125
- package/dist/main.d.ts +171 -125
- package/dist/main.js +967 -886
- package/dist/main.mjs +914 -834
- package/dist/renderer.d.mts +17 -2
- package/dist/renderer.d.ts +17 -2
- package/dist/renderer.js +161 -118
- package/dist/renderer.mjs +150 -106
- package/package.json +1 -1
- package/src/DI/app-injector.ts +22 -9
- package/src/DI/injector-explorer.ts +78 -20
- package/src/internal/app.ts +9 -7
- package/src/internal/bootstrap.ts +33 -1
- package/src/internal/renderer-client.ts +36 -0
- package/src/internal/request.ts +6 -1
- package/src/internal/router.ts +14 -2
- package/src/internal/routes.ts +75 -11
- package/src/internal/socket.ts +8 -6
- package/src/utils/radix-tree.ts +58 -25
- package/src/window/window-manager.ts +34 -0
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
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
package/src/DI/app-injector.ts
CHANGED
|
@@ -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
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
139
|
-
return (
|
|
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
|
|
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.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
if (reg.isController) reg.pathPrefix = pathPrefix;
|
|
83
|
-
}
|
|
109
|
+
InjectorExplorer._phaseTwo(queue, undefined, routeGuards, routeMiddlewares);
|
|
110
|
+
});
|
|
84
111
|
|
|
85
|
-
InjectorExplorer.
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/internal/app.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { app, BrowserWindow, ipcMain, MessageChannelMain } from 'electron/main';
|
|
8
|
-
import { Guard } from "
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
127
|
+
if (route.load) {
|
|
128
|
+
noxApp.lazy(route.path, route.load, route.guards, route.middlewares);
|
|
129
|
+
}
|
|
98
130
|
}
|
|
99
131
|
}
|
|
100
132
|
|