@noxfly/noxus 2.4.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 +1114 -1239
- package/dist/child.mjs +1090 -1193
- package/dist/main.d.mts +304 -261
- package/dist/main.d.ts +304 -261
- package/dist/main.js +1473 -1873
- package/dist/main.mjs +1423 -1791
- package/dist/renderer.d.mts +113 -2
- package/dist/renderer.d.ts +113 -2
- package/dist/renderer.js +144 -132
- package/dist/renderer.mjs +143 -132
- 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 +100 -81
- package/src/DI/token.ts +53 -0
- package/src/app.ts +141 -131
- package/src/bootstrap.ts +79 -40
- 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 +3 -0
- package/src/main.ts +4 -11
- 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 +221 -369
- 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/index-BxWQVi6C.d.ts +0 -253
- package/dist/index-DQBQQfMw.d.mts +0 -253
- 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,45 +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 {
|
|
12
|
-
import {
|
|
13
|
-
import { Router } from
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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";
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
+
* }
|
|
21
41
|
*/
|
|
22
42
|
export interface IApp {
|
|
23
43
|
dispose(): Promise<void>;
|
|
24
|
-
onReady(
|
|
44
|
+
onReady(): Promise<void>;
|
|
25
45
|
onActivated(): Promise<void>;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
|
-
|
|
29
|
-
* NoxApp is the main application class that manages the application lifecycle,
|
|
30
|
-
* handles IPC communication, and integrates with the Router.
|
|
31
|
-
*/
|
|
32
|
-
@Injectable('singleton')
|
|
48
|
+
@Injectable({ lifetime: 'singleton', deps: [Router, NoxSocket, WindowManager] })
|
|
33
49
|
export class NoxApp {
|
|
34
|
-
private
|
|
35
|
-
|
|
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
|
+
// -------------------------------------------------------------------------
|
|
36
71
|
|
|
37
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.
|
|
38
78
|
*
|
|
79
|
+
* @example
|
|
80
|
+
* noxApp.lazy('auth', () => import('./modules/auth/auth.controller.js'));
|
|
81
|
+
* noxApp.lazy('reporting', () => import('./modules/reporting/index.js'));
|
|
39
82
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
42
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;
|
|
43
138
|
const channels = this.socket.get(senderId);
|
|
44
139
|
|
|
45
|
-
if(!channels) {
|
|
140
|
+
if (!channels) {
|
|
46
141
|
Logger.error(`No message channel found for sender ID: ${senderId}`);
|
|
47
142
|
return;
|
|
48
143
|
}
|
|
@@ -52,49 +147,21 @@ export class NoxApp {
|
|
|
52
147
|
const response = await this.router.handle(request);
|
|
53
148
|
channels.request.port1.postMessage(response);
|
|
54
149
|
}
|
|
55
|
-
catch(err:
|
|
150
|
+
catch (err: unknown) {
|
|
56
151
|
const response: IResponse = {
|
|
57
152
|
requestId,
|
|
58
153
|
status: 500,
|
|
59
154
|
body: null,
|
|
60
|
-
error: err.message
|
|
155
|
+
error: err instanceof Error ? err.message : 'Internal Server Error',
|
|
61
156
|
};
|
|
62
|
-
|
|
63
157
|
channels.request.port1.postMessage(response);
|
|
64
158
|
}
|
|
65
159
|
};
|
|
66
160
|
|
|
67
|
-
constructor(
|
|
68
|
-
private readonly router: Router,
|
|
69
|
-
private readonly socket: NoxSocket,
|
|
70
|
-
) {}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Initializes the NoxApp instance.
|
|
74
|
-
* This method sets up the IPC communication, registers event listeners,
|
|
75
|
-
* and prepares the application for use.
|
|
76
|
-
*/
|
|
77
|
-
public async init(): Promise<NoxApp> {
|
|
78
|
-
ipcMain.on('gimme-my-port', this.giveTheRendererAPort.bind(this));
|
|
79
|
-
|
|
80
|
-
app.once('activate', this.onAppActivated.bind(this));
|
|
81
|
-
app.once('window-all-closed', this.onAllWindowsClosed.bind(this));
|
|
82
|
-
|
|
83
|
-
console.log(''); // create a new line in the console to separate setup logs from the future logs
|
|
84
|
-
|
|
85
|
-
return this;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Handles the request from the renderer process.
|
|
90
|
-
* This method creates a Request object from the IPC event data,
|
|
91
|
-
* processes it through the Router, and sends the response back
|
|
92
|
-
* to the renderer process using the MessageChannel.
|
|
93
|
-
*/
|
|
94
161
|
private giveTheRendererAPort(event: Electron.IpcMainInvokeEvent): void {
|
|
95
162
|
const senderId = event.sender.id;
|
|
96
163
|
|
|
97
|
-
if(this.socket.get(senderId)) {
|
|
164
|
+
if (this.socket.get(senderId)) {
|
|
98
165
|
this.shutdownChannel(senderId);
|
|
99
166
|
}
|
|
100
167
|
|
|
@@ -105,103 +172,46 @@ export class NoxApp {
|
|
|
105
172
|
requestChannel.port1.start();
|
|
106
173
|
socketChannel.port1.start();
|
|
107
174
|
|
|
108
|
-
|
|
175
|
+
event.sender.once('destroyed', () => this.shutdownChannel(senderId));
|
|
109
176
|
|
|
177
|
+
this.socket.register(senderId, requestChannel, socketChannel);
|
|
110
178
|
event.sender.postMessage('port', { senderId }, [requestChannel.port2, socketChannel.port2]);
|
|
111
179
|
}
|
|
112
180
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// Lifecycle
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
|
|
116
185
|
private onAppActivated(): void {
|
|
117
|
-
if(process.platform === 'darwin' && BrowserWindow.getAllWindows().length === 0) {
|
|
118
|
-
this.
|
|
186
|
+
if (process.platform === 'darwin' && BrowserWindow.getAllWindows().length === 0) {
|
|
187
|
+
this.appService?.onActivated();
|
|
119
188
|
}
|
|
120
189
|
}
|
|
121
190
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
|
|
129
202
|
private shutdownChannel(channelSenderId: number): void {
|
|
130
203
|
const channels = this.socket.get(channelSenderId);
|
|
131
204
|
|
|
132
|
-
if(!channels) {
|
|
133
|
-
Logger.warn(`No message channel found for sender ID: ${channelSenderId}`);
|
|
205
|
+
if (!channels) {
|
|
134
206
|
return;
|
|
135
207
|
}
|
|
136
208
|
|
|
137
209
|
channels.request.port1.off('message', this.onRendererMessage);
|
|
138
210
|
channels.request.port1.close();
|
|
139
211
|
channels.request.port2.close();
|
|
140
|
-
|
|
141
212
|
channels.socket.port1.close();
|
|
142
213
|
channels.socket.port2.close();
|
|
143
214
|
|
|
144
215
|
this.socket.unregister(channelSenderId);
|
|
145
216
|
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Handles the application shutdown process.
|
|
149
|
-
* This method is called when all windows are closed, and it cleans up the message channels
|
|
150
|
-
*/
|
|
151
|
-
private async onAllWindowsClosed(): Promise<void> {
|
|
152
|
-
for(const senderId of this.socket.getSenderIds()) {
|
|
153
|
-
this.shutdownChannel(senderId);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
Logger.info('All windows closed, shutting down application...');
|
|
157
|
-
await this.app?.dispose();
|
|
158
|
-
|
|
159
|
-
if(process.platform !== 'darwin') {
|
|
160
|
-
app.quit();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Configures the NoxApp instance with the provided application class.
|
|
178
|
-
* This method allows you to set the application class that will handle lifecycle events.
|
|
179
|
-
* @param app - The application class to configure.
|
|
180
|
-
* @returns NoxApp instance for method chaining.
|
|
181
|
-
*/
|
|
182
|
-
public configure(app: Type<IApp>): NoxApp {
|
|
183
|
-
this.app = inject(app);
|
|
184
|
-
return this;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Registers a middleware for the root of the application.
|
|
189
|
-
* This method allows you to define a middleware that will be applied to all requests
|
|
190
|
-
* @param middleware - The middleware class to register.
|
|
191
|
-
* @returns NoxApp instance for method chaining.
|
|
192
|
-
*/
|
|
193
|
-
public use(middleware: Type<IMiddleware>): NoxApp {
|
|
194
|
-
this.router.defineRootMiddleware(middleware);
|
|
195
|
-
return this;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Should be called after the bootstrapApplication function is called.
|
|
200
|
-
* Passes the early-created BrowserWindow (if any) to the configured IApp service.
|
|
201
|
-
* @returns NoxApp instance for method chaining.
|
|
202
|
-
*/
|
|
203
|
-
public start(): NoxApp {
|
|
204
|
-
this.app?.onReady(this.mainWindow);
|
|
205
|
-
return this;
|
|
206
|
-
}
|
|
207
217
|
}
|
package/src/bootstrap.ts
CHANGED
|
@@ -4,66 +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(!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);
|
|
54
85
|
}
|
|
55
86
|
|
|
56
|
-
//
|
|
57
|
-
InjectorExplorer.processPending();
|
|
87
|
+
// Flush all classes enqueued by decorators at import time (two-phase)
|
|
88
|
+
InjectorExplorer.processPending(overrides);
|
|
58
89
|
|
|
90
|
+
// Resolve core framework singletons
|
|
59
91
|
const noxApp = inject(NoxApp);
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
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);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Eagerly load optional modules
|
|
101
|
+
if (config.eagerLoad?.length) {
|
|
102
|
+
await noxApp.load(config.eagerLoad);
|
|
63
103
|
}
|
|
64
104
|
|
|
65
105
|
await noxApp.init();
|
|
66
106
|
|
|
67
107
|
return noxApp;
|
|
68
108
|
}
|
|
69
|
-
|
|
@@ -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');
|
|
@@ -4,71 +4,12 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Request } from '
|
|
8
|
-
import {
|
|
9
|
-
import { MaybeAsync, Type } from 'src/utils/types';
|
|
7
|
+
import { Request } from '../request';
|
|
8
|
+
import { MaybeAsync } from '../utils/types';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* The `canActivate` method can return either a value or a Promise.
|
|
15
|
-
* Use it on a class that should be registered as a guard in the application.
|
|
16
|
-
* Guards can be used to protect routes or controller actions.
|
|
17
|
-
* For example, you can use guards to check if the user is authenticated or has the right permissions.
|
|
18
|
-
* You can use the `Authorize` decorator to register guards for a controller or a controller action.
|
|
19
|
-
* @see Authorize
|
|
11
|
+
* A guard decides whether an incoming request should reach the handler.
|
|
12
|
+
* Implement this interface and pass the class to @Controller({ guards }) or @Get('path', { guards }).
|
|
20
13
|
*/
|
|
21
|
-
export interface IGuard {
|
|
22
|
-
canActivate(request: Request): MaybeAsync<boolean>;
|
|
23
|
-
}
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
* Can be used to protect the routes of a controller.
|
|
27
|
-
* Can be used on a controller class or on a controller method.
|
|
28
|
-
*/
|
|
29
|
-
export function Authorize(...guardClasses: Type<IGuard>[]): MethodDecorator & ClassDecorator {
|
|
30
|
-
return (target: Function | object, propertyKey?: string | symbol) => {
|
|
31
|
-
let key: string;
|
|
32
|
-
|
|
33
|
-
// Method decorator
|
|
34
|
-
if(propertyKey) {
|
|
35
|
-
const ctrlName = target.constructor.name;
|
|
36
|
-
const actionName = propertyKey as string;
|
|
37
|
-
key = `${ctrlName}.${actionName}`;
|
|
38
|
-
}
|
|
39
|
-
// Class decorator
|
|
40
|
-
else {
|
|
41
|
-
const ctrlName = (target as Type<unknown>).name;
|
|
42
|
-
key = `${ctrlName}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if(authorizations.has(key)) {
|
|
46
|
-
throw new Error(`Guard(s) already registered for ${key}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
authorizations.set(key, guardClasses);
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Gets the guards for a controller or a controller action.
|
|
55
|
-
* @param controllerName The name of the controller to get the guards for.
|
|
56
|
-
* @returns An array of guards for the controller.
|
|
57
|
-
*/
|
|
58
|
-
export function getGuardForController(controllerName: string): Type<IGuard>[] {
|
|
59
|
-
const key = `${controllerName}`;
|
|
60
|
-
return authorizations.get(key) ?? [];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Gets the guards for a controller action.
|
|
65
|
-
* @param controllerName The name of the controller to get the guards for.
|
|
66
|
-
* @param actionName The name of the action to get the guards for.
|
|
67
|
-
* @returns An array of guards for the controller action.
|
|
68
|
-
*/
|
|
69
|
-
export function getGuardForControllerAction(controllerName: string, actionName: string): Type<IGuard>[] {
|
|
70
|
-
const key = `${controllerName}.${actionName}`;
|
|
71
|
-
return authorizations.get(key) ?? [];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const authorizations = new Map<string, Type<IGuard>[]>();
|
|
15
|
+
export type Guard = (request: Request) => MaybeAsync<boolean>;
|