@noxfly/noxus 2.5.0 → 3.0.0-dev.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/README.md +403 -341
- package/dist/app-injector-Bz3Upc0y.d.mts +125 -0
- package/dist/app-injector-Bz3Upc0y.d.ts +125 -0
- package/dist/child.d.mts +48 -22
- package/dist/child.d.ts +48 -22
- package/dist/child.js +1111 -1341
- package/dist/child.mjs +1087 -1295
- package/dist/main.d.mts +301 -309
- package/dist/main.d.ts +301 -309
- package/dist/main.js +1471 -1650
- package/dist/main.mjs +1420 -1570
- package/dist/renderer.d.mts +3 -3
- package/dist/renderer.d.ts +3 -3
- package/dist/renderer.js +109 -135
- package/dist/renderer.mjs +109 -135
- package/dist/request-BlTtiHbi.d.ts +112 -0
- package/dist/request-qJ9EiDZc.d.mts +112 -0
- package/package.json +7 -7
- package/src/DI/app-injector.ts +95 -106
- package/src/DI/injector-explorer.ts +93 -119
- package/src/DI/token.ts +53 -0
- package/src/app.ts +141 -168
- package/src/bootstrap.ts +78 -54
- package/src/decorators/controller.decorator.ts +38 -27
- package/src/decorators/guards.decorator.ts +5 -64
- package/src/decorators/injectable.decorator.ts +68 -15
- package/src/decorators/method.decorator.ts +40 -81
- package/src/decorators/middleware.decorator.ts +5 -72
- package/src/index.ts +2 -0
- package/src/main.ts +4 -8
- package/src/non-electron-process.ts +0 -1
- package/src/preload-bridge.ts +1 -1
- package/src/renderer-client.ts +2 -2
- package/src/renderer-events.ts +1 -1
- package/src/request.ts +3 -3
- package/src/router.ts +190 -431
- package/src/routes.ts +78 -0
- package/src/socket.ts +4 -4
- package/src/window/window-manager.ts +255 -0
- package/tsconfig.json +5 -10
- package/tsup.config.ts +2 -2
- package/dist/app-injector-B3MvgV3k.d.mts +0 -95
- package/dist/app-injector-B3MvgV3k.d.ts +0 -95
- package/dist/request-CdpZ9qZL.d.ts +0 -167
- package/dist/request-Dx_5Prte.d.mts +0 -167
- package/src/decorators/inject.decorator.ts +0 -24
- package/src/decorators/injectable.metadata.ts +0 -15
- package/src/decorators/module.decorator.ts +0 -75
package/src/app.ts
CHANGED
|
@@ -4,46 +4,140 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { app, BrowserWindow, ipcMain, MessageChannelMain } from
|
|
8
|
-
import { Injectable } from
|
|
9
|
-
import {
|
|
10
|
-
import { inject } from
|
|
11
|
-
import { InjectorExplorer } from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { Logger } from
|
|
16
|
-
import { Type } from
|
|
7
|
+
import { app, BrowserWindow, ipcMain, MessageChannelMain } from 'electron/main';
|
|
8
|
+
import { Injectable } from './decorators/injectable.decorator';
|
|
9
|
+
import { Middleware } from './decorators/middleware.decorator';
|
|
10
|
+
import { inject } from './DI/app-injector';
|
|
11
|
+
import { InjectorExplorer } from './DI/injector-explorer';
|
|
12
|
+
import { IResponse, Request } from './request';
|
|
13
|
+
import { Router } from './router';
|
|
14
|
+
import { NoxSocket } from './socket';
|
|
15
|
+
import { Logger } from './utils/logger';
|
|
16
|
+
import { Type } from './utils/types';
|
|
17
|
+
import { WindowManager } from './window/window-manager';
|
|
18
|
+
import { Guard } from "src/main";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* Your application service should implement IApp.
|
|
22
|
+
* Noxus calls these lifecycle methods at the appropriate time.
|
|
23
|
+
*
|
|
24
|
+
* Unlike v2, IApp no longer receives a BrowserWindow in onReady.
|
|
25
|
+
* Use the injected WindowManager instead — it is more flexible and
|
|
26
|
+
* does not couple the lifecycle to a single pre-created window.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* @Injectable({ lifetime: 'singleton', deps: [WindowManager, MyService] })
|
|
30
|
+
* class AppService implements IApp {
|
|
31
|
+
* constructor(private wm: WindowManager, private svc: MyService) {}
|
|
32
|
+
*
|
|
33
|
+
* async onReady() {
|
|
34
|
+
* const win = await this.wm.createSplash({ webPreferences: { preload: ... } });
|
|
35
|
+
* win.loadFile('index.html');
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* async onActivated() { ... }
|
|
39
|
+
* async dispose() { ... }
|
|
40
|
+
* }
|
|
22
41
|
*/
|
|
23
42
|
export interface IApp {
|
|
24
43
|
dispose(): Promise<void>;
|
|
25
|
-
onReady(
|
|
44
|
+
onReady(): Promise<void>;
|
|
26
45
|
onActivated(): Promise<void>;
|
|
27
46
|
}
|
|
28
47
|
|
|
29
|
-
|
|
30
|
-
* NoxApp is the main application class that manages the application lifecycle,
|
|
31
|
-
* handles IPC communication, and integrates with the Router.
|
|
32
|
-
*/
|
|
33
|
-
@Injectable('singleton')
|
|
48
|
+
@Injectable({ lifetime: 'singleton', deps: [Router, NoxSocket, WindowManager] })
|
|
34
49
|
export class NoxApp {
|
|
35
|
-
private
|
|
36
|
-
|
|
50
|
+
private appService: IApp | undefined;
|
|
51
|
+
|
|
52
|
+
private readonly router = inject(Router);
|
|
53
|
+
private readonly socket = inject(NoxSocket);
|
|
54
|
+
public readonly windowManager = inject(WindowManager);
|
|
55
|
+
|
|
56
|
+
// -------------------------------------------------------------------------
|
|
57
|
+
// Initialisation
|
|
58
|
+
// -------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
public async init(): Promise<this> {
|
|
61
|
+
ipcMain.on('gimme-my-port', this.giveTheRendererAPort.bind(this));
|
|
62
|
+
app.once('activate', this.onAppActivated.bind(this));
|
|
63
|
+
app.once('window-all-closed', this.onAllWindowsClosed.bind(this));
|
|
64
|
+
console.log('');
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
// Public API
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
37
71
|
|
|
38
72
|
/**
|
|
73
|
+
* Registers a lazy route. The file behind this prefix is dynamically
|
|
74
|
+
* imported on the first IPC request that targets it.
|
|
75
|
+
*
|
|
76
|
+
* The import function should NOT statically reference heavy modules —
|
|
77
|
+
* the whole point is to defer their loading.
|
|
39
78
|
*
|
|
79
|
+
* @example
|
|
80
|
+
* noxApp.lazy('auth', () => import('./modules/auth/auth.controller.js'));
|
|
81
|
+
* noxApp.lazy('reporting', () => import('./modules/reporting/index.js'));
|
|
40
82
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
83
|
+
public lazy(
|
|
84
|
+
pathPrefix: string,
|
|
85
|
+
load: () => Promise<unknown>,
|
|
86
|
+
guards: Guard[] = [],
|
|
87
|
+
middlewares: Middleware[] = [],
|
|
88
|
+
): this {
|
|
89
|
+
this.router.registerLazyRoute(pathPrefix, load, guards, middlewares);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
43
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Eagerly loads a set of modules (controllers + services) before start().
|
|
95
|
+
* Use this for modules that provide services needed by your IApp.onReady().
|
|
96
|
+
*
|
|
97
|
+
* All imports run in parallel; DI is flushed with the two-phase guarantee.
|
|
98
|
+
*/
|
|
99
|
+
public async load(importFns: Array<() => Promise<unknown>>): Promise<this> {
|
|
100
|
+
InjectorExplorer.beginAccumulate();
|
|
101
|
+
await Promise.all(importFns.map((fn) => fn()));
|
|
102
|
+
InjectorExplorer.flushAccumulated();
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Registers a global middleware applied to every route.
|
|
108
|
+
*/
|
|
109
|
+
public use(middleware: Middleware): this {
|
|
110
|
+
this.router.defineRootMiddleware(middleware);
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Sets the application service (implements IApp) that receives lifecycle events.
|
|
116
|
+
* @param appClass - Class decorated with @Injectable that implements IApp.
|
|
117
|
+
*/
|
|
118
|
+
public configure(appClass: Type<IApp>): this {
|
|
119
|
+
this.appService = inject(appClass);
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Calls IApp.onReady(). Should be called after configure() and any lazy()
|
|
125
|
+
* registrations are set up.
|
|
126
|
+
*/
|
|
127
|
+
public start(): this {
|
|
128
|
+
this.appService?.onReady();
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
// IPC
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
private readonly onRendererMessage = async (event: Electron.MessageEvent): Promise<void> => {
|
|
137
|
+
const { senderId, requestId, path, method, body }: import('./request').IRequest = event.data;
|
|
44
138
|
const channels = this.socket.get(senderId);
|
|
45
139
|
|
|
46
|
-
if(!channels) {
|
|
140
|
+
if (!channels) {
|
|
47
141
|
Logger.error(`No message channel found for sender ID: ${senderId}`);
|
|
48
142
|
return;
|
|
49
143
|
}
|
|
@@ -53,49 +147,21 @@ export class NoxApp {
|
|
|
53
147
|
const response = await this.router.handle(request);
|
|
54
148
|
channels.request.port1.postMessage(response);
|
|
55
149
|
}
|
|
56
|
-
catch(err:
|
|
150
|
+
catch (err: unknown) {
|
|
57
151
|
const response: IResponse = {
|
|
58
152
|
requestId,
|
|
59
153
|
status: 500,
|
|
60
154
|
body: null,
|
|
61
|
-
error: err.message
|
|
155
|
+
error: err instanceof Error ? err.message : 'Internal Server Error',
|
|
62
156
|
};
|
|
63
|
-
|
|
64
157
|
channels.request.port1.postMessage(response);
|
|
65
158
|
}
|
|
66
159
|
};
|
|
67
160
|
|
|
68
|
-
constructor(
|
|
69
|
-
private readonly router: Router,
|
|
70
|
-
private readonly socket: NoxSocket,
|
|
71
|
-
) {}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Initializes the NoxApp instance.
|
|
75
|
-
* This method sets up the IPC communication, registers event listeners,
|
|
76
|
-
* and prepares the application for use.
|
|
77
|
-
*/
|
|
78
|
-
public async init(): Promise<NoxApp> {
|
|
79
|
-
ipcMain.on('gimme-my-port', this.giveTheRendererAPort.bind(this));
|
|
80
|
-
|
|
81
|
-
app.once('activate', this.onAppActivated.bind(this));
|
|
82
|
-
app.once('window-all-closed', this.onAllWindowsClosed.bind(this));
|
|
83
|
-
|
|
84
|
-
console.log(''); // create a new line in the console to separate setup logs from the future logs
|
|
85
|
-
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Handles the request from the renderer process.
|
|
91
|
-
* This method creates a Request object from the IPC event data,
|
|
92
|
-
* processes it through the Router, and sends the response back
|
|
93
|
-
* to the renderer process using the MessageChannel.
|
|
94
|
-
*/
|
|
95
161
|
private giveTheRendererAPort(event: Electron.IpcMainInvokeEvent): void {
|
|
96
162
|
const senderId = event.sender.id;
|
|
97
163
|
|
|
98
|
-
if(this.socket.get(senderId)) {
|
|
164
|
+
if (this.socket.get(senderId)) {
|
|
99
165
|
this.shutdownChannel(senderId);
|
|
100
166
|
}
|
|
101
167
|
|
|
@@ -106,139 +172,46 @@ export class NoxApp {
|
|
|
106
172
|
requestChannel.port1.start();
|
|
107
173
|
socketChannel.port1.start();
|
|
108
174
|
|
|
109
|
-
|
|
175
|
+
event.sender.once('destroyed', () => this.shutdownChannel(senderId));
|
|
110
176
|
|
|
177
|
+
this.socket.register(senderId, requestChannel, socketChannel);
|
|
111
178
|
event.sender.postMessage('port', { senderId }, [requestChannel.port2, socketChannel.port2]);
|
|
112
179
|
}
|
|
113
180
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// Lifecycle
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
|
|
117
185
|
private onAppActivated(): void {
|
|
118
|
-
if(process.platform === 'darwin' && BrowserWindow.getAllWindows().length === 0) {
|
|
119
|
-
this.
|
|
186
|
+
if (process.platform === 'darwin' && BrowserWindow.getAllWindows().length === 0) {
|
|
187
|
+
this.appService?.onActivated();
|
|
120
188
|
}
|
|
121
189
|
}
|
|
122
190
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
191
|
+
private async onAllWindowsClosed(): Promise<void> {
|
|
192
|
+
for (const senderId of this.socket.getSenderIds()) {
|
|
193
|
+
this.shutdownChannel(senderId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
Logger.info('All windows closed, shutting down application...');
|
|
197
|
+
await this.appService?.dispose();
|
|
198
|
+
|
|
199
|
+
if (process.platform !== 'darwin') app.quit();
|
|
200
|
+
}
|
|
201
|
+
|
|
130
202
|
private shutdownChannel(channelSenderId: number): void {
|
|
131
203
|
const channels = this.socket.get(channelSenderId);
|
|
132
204
|
|
|
133
|
-
if(!channels) {
|
|
134
|
-
Logger.warn(`No message channel found for sender ID: ${channelSenderId}`);
|
|
205
|
+
if (!channels) {
|
|
135
206
|
return;
|
|
136
207
|
}
|
|
137
208
|
|
|
138
209
|
channels.request.port1.off('message', this.onRendererMessage);
|
|
139
210
|
channels.request.port1.close();
|
|
140
211
|
channels.request.port2.close();
|
|
141
|
-
|
|
142
212
|
channels.socket.port1.close();
|
|
143
213
|
channels.socket.port2.close();
|
|
144
214
|
|
|
145
215
|
this.socket.unregister(channelSenderId);
|
|
146
216
|
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Handles the application shutdown process.
|
|
150
|
-
* This method is called when all windows are closed, and it cleans up the message channels
|
|
151
|
-
*/
|
|
152
|
-
private async onAllWindowsClosed(): Promise<void> {
|
|
153
|
-
for(const senderId of this.socket.getSenderIds()) {
|
|
154
|
-
this.shutdownChannel(senderId);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
Logger.info('All windows closed, shutting down application...');
|
|
158
|
-
await this.app?.dispose();
|
|
159
|
-
|
|
160
|
-
if(process.platform !== 'darwin') {
|
|
161
|
-
app.quit();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// ---
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Sets the main BrowserWindow that was created early by bootstrapApplication.
|
|
170
|
-
* This window will be passed to IApp.onReady when start() is called.
|
|
171
|
-
* @param window - The BrowserWindow created during bootstrap.
|
|
172
|
-
*/
|
|
173
|
-
public setMainWindow(window: BrowserWindow): void {
|
|
174
|
-
this.mainWindow = window;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Registers a lazy-loaded route. The module behind this path prefix
|
|
179
|
-
* will only be dynamically imported when the first IPC request
|
|
180
|
-
* targets this prefix — like Angular's loadChildren.
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```ts
|
|
184
|
-
* noxApp.lazy("auth", () => import("./modules/auth/auth.module.js"));
|
|
185
|
-
* noxApp.lazy("printing", () => import("./modules/printing/printing.module.js"));
|
|
186
|
-
* ```
|
|
187
|
-
*
|
|
188
|
-
* @param pathPrefix - The route prefix (e.g. "auth", "cash-register").
|
|
189
|
-
* @param loadModule - A function returning a dynamic import promise.
|
|
190
|
-
* @returns NoxApp instance for method chaining.
|
|
191
|
-
*/
|
|
192
|
-
public lazy(pathPrefix: string, loadModule: () => Promise<unknown>): NoxApp {
|
|
193
|
-
this.router.registerLazyRoute(pathPrefix, loadModule);
|
|
194
|
-
return this;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Eagerly loads one or more modules with a two-phase DI guarantee.
|
|
199
|
-
* Use this when a service needed at startup lives inside a module
|
|
200
|
-
* (e.g. the Application service depends on LoaderService).
|
|
201
|
-
*
|
|
202
|
-
* All dynamic imports run in parallel; bindings are registered first,
|
|
203
|
-
* then singletons are resolved — safe regardless of import ordering.
|
|
204
|
-
*
|
|
205
|
-
* @param importFns - Functions returning dynamic import promises.
|
|
206
|
-
*/
|
|
207
|
-
public async loadModules(importFns: Array<() => Promise<unknown>>): Promise<void> {
|
|
208
|
-
InjectorExplorer.beginAccumulate();
|
|
209
|
-
await Promise.all(importFns.map(fn => fn()));
|
|
210
|
-
InjectorExplorer.flushAccumulated();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Configures the NoxApp instance with the provided application class.
|
|
215
|
-
* This method allows you to set the application class that will handle lifecycle events.
|
|
216
|
-
* @param app - The application class to configure.
|
|
217
|
-
* @returns NoxApp instance for method chaining.
|
|
218
|
-
*/
|
|
219
|
-
public configure(app: Type<IApp>): NoxApp {
|
|
220
|
-
this.app = inject(app);
|
|
221
|
-
return this;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Registers a middleware for the root of the application.
|
|
226
|
-
* This method allows you to define a middleware that will be applied to all requests
|
|
227
|
-
* @param middleware - The middleware class to register.
|
|
228
|
-
* @returns NoxApp instance for method chaining.
|
|
229
|
-
*/
|
|
230
|
-
public use(middleware: Type<IMiddleware>): NoxApp {
|
|
231
|
-
this.router.defineRootMiddleware(middleware);
|
|
232
|
-
return this;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Should be called after the bootstrapApplication function is called.
|
|
237
|
-
* Passes the early-created BrowserWindow (if any) to the configured IApp service.
|
|
238
|
-
* @returns NoxApp instance for method chaining.
|
|
239
|
-
*/
|
|
240
|
-
public start(): NoxApp {
|
|
241
|
-
this.app?.onReady(this.mainWindow);
|
|
242
|
-
return this;
|
|
243
|
-
}
|
|
244
217
|
}
|
package/src/bootstrap.ts
CHANGED
|
@@ -4,81 +4,105 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { app
|
|
8
|
-
import { NoxApp } from
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { app } from 'electron/main';
|
|
8
|
+
import { NoxApp } from './app';
|
|
9
|
+
import { inject, RootInjector } from './DI/app-injector';
|
|
10
|
+
import { InjectorExplorer } from './DI/injector-explorer';
|
|
11
|
+
import { TokenKey } from './DI/token';
|
|
12
|
+
import { RouteDefinition } from "src/routes";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* A singleton value override: provides an already-constructed instance
|
|
16
|
+
* for a given token, bypassing the DI factory.
|
|
17
|
+
*
|
|
18
|
+
* Useful for injecting external singletons (e.g. a database connection,
|
|
19
|
+
* a logger already configured, a third-party SDK wrapper) that cannot
|
|
20
|
+
* or should not be constructed by the DI container.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* { token: MikroORM, useValue: await MikroORM.init(config) }
|
|
24
|
+
* { token: DB_URL, useValue: process.env.DATABASE_URL }
|
|
25
|
+
*/
|
|
26
|
+
export interface SingletonOverride<T = unknown> {
|
|
27
|
+
token: TokenKey<T>;
|
|
28
|
+
useValue: T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration object for bootstrapApplication.
|
|
16
33
|
*/
|
|
17
|
-
export interface
|
|
34
|
+
export interface BootstrapConfig {
|
|
35
|
+
/**
|
|
36
|
+
* Application routing table, produced by defineRoutes().
|
|
37
|
+
* All lazy routes are registered before the app starts.
|
|
38
|
+
*/
|
|
39
|
+
routes?: RouteDefinition[];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Pre-built singleton instances to inject into the DI container
|
|
43
|
+
* before the application starts.
|
|
44
|
+
*
|
|
45
|
+
* This replaces the v2 module/provider declaration pattern for
|
|
46
|
+
* external singletons.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* singletons: [
|
|
50
|
+
* { token: MikroORM, useValue: await MikroORM.init(ormConfig) },
|
|
51
|
+
* { token: DB_URL, useValue: process.env.DATABASE_URL! },
|
|
52
|
+
* ]
|
|
53
|
+
*/
|
|
54
|
+
singletons?: SingletonOverride[];
|
|
55
|
+
|
|
18
56
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
57
|
+
* Controllers and services to eagerly load before NoxApp.start() is called.
|
|
58
|
+
* Each entry is a dynamic import function — files are imported in parallel.
|
|
59
|
+
*
|
|
60
|
+
* Use this only for things needed at startup (e.g. if your IApp service
|
|
61
|
+
* depends on a service in an otherwise lazy module).
|
|
62
|
+
*
|
|
63
|
+
* Everything else should be registered via noxApp.lazy().
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* eagerLoad: [
|
|
67
|
+
* () => import('./modules/auth/auth.controller.js'),
|
|
68
|
+
* ]
|
|
23
69
|
*/
|
|
24
|
-
|
|
70
|
+
eagerLoad?: Array<() => Promise<unknown>>;
|
|
25
71
|
}
|
|
26
72
|
|
|
27
73
|
/**
|
|
28
74
|
* Bootstraps the Noxus application.
|
|
29
|
-
* This function initializes the application by creating an instance of NoxApp,
|
|
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
|
-
*
|
|
36
|
-
* @param rootModule - The root module of the application, decorated with @Module.
|
|
37
|
-
* @param options - Optional bootstrap configuration.
|
|
38
|
-
* @return A promise that resolves to the NoxApp instance.
|
|
39
|
-
* @throws Error if the root module is not decorated with @Module, or if the electron process could not start.
|
|
40
75
|
*/
|
|
41
|
-
export async function bootstrapApplication(
|
|
42
|
-
if(rootModule && !getModuleMetadata(rootModule)) {
|
|
43
|
-
throw new Error(`Root module must be decorated with @Module`);
|
|
44
|
-
}
|
|
45
|
-
|
|
76
|
+
export async function bootstrapApplication(config: BootstrapConfig = {}): Promise<NoxApp> {
|
|
46
77
|
await app.whenReady();
|
|
47
78
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
// Build override map for the DI flush phase
|
|
80
|
+
const overrides = new Map<TokenKey, unknown>();
|
|
81
|
+
for (const { token, useValue } of config.singletons ?? []) {
|
|
82
|
+
overrides.set(token, useValue);
|
|
83
|
+
// Pre-register the binding so the injector knows the token exists
|
|
84
|
+
RootInjector.singletons.set(token as any, useValue);
|
|
85
|
+
}
|
|
54
86
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
87
|
+
// Flush all classes enqueued by decorators at import time (two-phase)
|
|
88
|
+
InjectorExplorer.processPending(overrides);
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
// Resolve core framework singletons
|
|
91
|
+
const noxApp = inject(NoxApp);
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
true,
|
|
67
|
-
);
|
|
93
|
+
// Register routes from the routing table
|
|
94
|
+
if (config.routes?.length) {
|
|
95
|
+
for (const route of config.routes) {
|
|
96
|
+
noxApp.lazy(route.path, route.load, route.guards, route.middlewares);
|
|
68
97
|
}
|
|
69
98
|
}
|
|
70
99
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const noxApp = inject(NoxApp);
|
|
75
|
-
|
|
76
|
-
if(mainWindow) {
|
|
77
|
-
noxApp.setMainWindow(mainWindow);
|
|
100
|
+
// Eagerly load optional modules
|
|
101
|
+
if (config.eagerLoad?.length) {
|
|
102
|
+
await noxApp.load(config.eagerLoad);
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
await noxApp.init();
|
|
81
106
|
|
|
82
107
|
return noxApp;
|
|
83
108
|
}
|
|
84
|
-
|
|
@@ -4,44 +4,55 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { Type } from
|
|
7
|
+
import { InjectorExplorer } from '../DI/injector-explorer';
|
|
8
|
+
import { TokenKey } from '../DI/token';
|
|
9
|
+
import { Type } from '../utils/types';
|
|
10
|
+
|
|
11
|
+
export interface ControllerOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Explicit constructor dependencies.
|
|
14
|
+
*/
|
|
15
|
+
deps?: ReadonlyArray<TokenKey>;
|
|
16
|
+
}
|
|
10
17
|
|
|
11
|
-
/**
|
|
12
|
-
* The configuration that waits a controller's decorator.
|
|
13
|
-
*/
|
|
14
18
|
export interface IControllerMetadata {
|
|
15
|
-
|
|
16
|
-
guards: Type<IGuard>[];
|
|
19
|
+
deps: ReadonlyArray<TokenKey>;
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
const controllerMetaMap = new WeakMap<object, IControllerMetadata>();
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
25
|
+
* Marks a class as a Noxus controller.
|
|
26
|
+
* Controllers are always scope-scoped injectables.
|
|
27
|
+
* The route prefix and guards/middlewares are declared in defineRoutes(), not here.
|
|
22
28
|
*
|
|
23
|
-
* @
|
|
29
|
+
* @example
|
|
30
|
+
* @Controller({ deps: [UserService] })
|
|
31
|
+
* export class UserController {
|
|
32
|
+
* constructor(private svc: UserService) {}
|
|
33
|
+
*
|
|
34
|
+
* @Get('byId/:userId')
|
|
35
|
+
* getUserById(req: Request) { ... }
|
|
36
|
+
* }
|
|
24
37
|
*/
|
|
25
|
-
export function Controller(
|
|
38
|
+
export function Controller(options: ControllerOptions = {}): ClassDecorator {
|
|
26
39
|
return (target) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
guards: getGuardForController(target.name)
|
|
40
|
+
const meta: IControllerMetadata = {
|
|
41
|
+
deps: options.deps ?? [],
|
|
30
42
|
};
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
controllerMetaMap.set(target, meta);
|
|
45
|
+
|
|
46
|
+
InjectorExplorer.enqueue({
|
|
47
|
+
key: target as unknown as Type<unknown>,
|
|
48
|
+
implementation: target as unknown as Type<unknown>,
|
|
49
|
+
lifetime: 'scope',
|
|
50
|
+
deps: options.deps ?? [],
|
|
51
|
+
isController: true,
|
|
52
|
+
});
|
|
34
53
|
};
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* This metadata includes the path and guards defined by the @Controller decorator.
|
|
40
|
-
* @param target - The target class to get the controller metadata from.
|
|
41
|
-
* @returns The controller metadata if it exists, otherwise undefined.
|
|
42
|
-
*/
|
|
43
|
-
export function getControllerMetadata(target: Type<unknown>): IControllerMetadata | undefined {
|
|
44
|
-
return Reflect.getMetadata(CONTROLLER_METADATA_KEY, target);
|
|
56
|
+
export function getControllerMetadata(target: object): IControllerMetadata | undefined {
|
|
57
|
+
return controllerMetaMap.get(target);
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
export const CONTROLLER_METADATA_KEY = Symbol('CONTROLLER_METADATA_KEY');
|