@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/README.md
CHANGED
|
@@ -1,508 +1,570 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @noxfly/noxus
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lightweight IPC framework for Electron, inspired by NestJS and Angular. It simulates HTTP-like requests between the renderer and the main process via `MessageChannel` / `MessagePort`, with routing, DI, guards, middlewares, and lazy-loading.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
No dependency on `reflect-metadata` or `emitDecoratorMetadata`.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Noxus fills that gap.
|
|
10
|
-
|
|
11
|
-
✅ Use of decorators
|
|
12
|
-
|
|
13
|
-
✅ Use of dependency injection, with lifetimes management (singleton, scope, transient)
|
|
14
|
-
|
|
15
|
-
✅ Modular architecture with the use of modules, defining a map of controllers and services
|
|
16
|
-
|
|
17
|
-
✅ Automatic and performant controller and route registration with path and method mapping
|
|
18
|
-
|
|
19
|
-
✅ A true request/response model built on top of MessagePort to look like HTTP requests
|
|
20
|
-
|
|
21
|
-
✅ Custom exception handling and unified error responses
|
|
22
|
-
|
|
23
|
-
✅ Decorator-based guard system for route and controller authorization
|
|
24
|
-
|
|
25
|
-
✅ Scoped dependency injection per request context
|
|
26
|
-
|
|
27
|
-
✅ Setup the electron application and communication with your renderer easily and with flexibility
|
|
28
|
-
|
|
29
|
-
✅ TypeScript-first with full type safety and metadata reflection
|
|
30
|
-
|
|
31
|
-
✅ Pluggable logging with color-coded output for different log levels
|
|
32
|
-
|
|
33
|
-
<sub>* If you see any issue and you'd like to enhance this framework, feel free to open an issue, fork and do a pull request.</sub>
|
|
7
|
+
---
|
|
34
8
|
|
|
35
9
|
## Installation
|
|
36
10
|
|
|
37
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install @noxfly/noxus
|
|
13
|
+
```
|
|
38
14
|
|
|
39
|
-
|
|
40
|
-
|
|
15
|
+
In your `tsconfig.json`:
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"compilerOptions": {
|
|
19
|
+
"experimentalDecorators": true,
|
|
20
|
+
"emitDecoratorMetadata": false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
41
23
|
```
|
|
42
24
|
|
|
43
|
-
|
|
25
|
+
---
|
|
44
26
|
|
|
45
|
-
##
|
|
27
|
+
## Concepts
|
|
46
28
|
|
|
47
|
-
|
|
29
|
+
| Concept | Role |
|
|
30
|
+
| ------------------------ | ----------------------------------------------------- |
|
|
31
|
+
| **Controller** | Handles a set of IPC routes under a path prefix |
|
|
32
|
+
| **Injectable** | Service injectable into other services or controllers |
|
|
33
|
+
| **Guard** | Protects a route or controller (authorization) |
|
|
34
|
+
| **Middleware** | Runs before guards and the handler |
|
|
35
|
+
| **Token** | Explicit identifier for non-class dependencies |
|
|
36
|
+
| **WindowManager** | Singleton service for managing Electron windows |
|
|
37
|
+
| **bootstrapApplication** | Single entry point of the application |
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
---
|
|
50
40
|
|
|
51
|
-
|
|
41
|
+
## Quick start
|
|
52
42
|
|
|
53
|
-
###
|
|
43
|
+
### 1. Create a service
|
|
54
44
|
|
|
55
45
|
```ts
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
import { bootstrapApplication } from '@noxfly/noxus/main';
|
|
59
|
-
import { AppModule } from './modules/app.module.ts';
|
|
60
|
-
import { Application } from './modules/app.service.ts';
|
|
46
|
+
// services/user.service.ts
|
|
47
|
+
import { Injectable } from '@noxfly/noxus/main';
|
|
61
48
|
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
@Injectable({ lifetime: 'singleton' })
|
|
50
|
+
export class UserService {
|
|
51
|
+
private users = [{ id: 1, name: 'Alice' }];
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
findAll() {
|
|
54
|
+
return this.users;
|
|
55
|
+
}
|
|
66
56
|
|
|
67
|
-
|
|
57
|
+
findById(id: number) {
|
|
58
|
+
return this.users.find(u => u.id === id);
|
|
59
|
+
}
|
|
68
60
|
}
|
|
69
|
-
|
|
70
|
-
main();
|
|
71
61
|
```
|
|
72
62
|
|
|
73
|
-
|
|
63
|
+
### 2. Create a controller
|
|
74
64
|
|
|
75
65
|
```ts
|
|
76
|
-
//
|
|
66
|
+
// controllers/user.controller.ts
|
|
67
|
+
import { Controller, Get, Post, Request } from '@noxfly/noxus/main';
|
|
68
|
+
import { UserService } from '../services/user.service';
|
|
77
69
|
|
|
78
|
-
|
|
70
|
+
@Controller({ path: 'users', deps: [UserService] })
|
|
71
|
+
export class UserController {
|
|
72
|
+
constructor(private svc: UserService) {}
|
|
79
73
|
|
|
80
|
-
@
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
@Get('list')
|
|
75
|
+
list(req: Request) {
|
|
76
|
+
return this.svc.findAll();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Get(':id')
|
|
80
|
+
getOne(req: Request) {
|
|
81
|
+
const id = parseInt(req.params['id']!);
|
|
82
|
+
return this.svc.findById(id);
|
|
83
|
+
}
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Logger.info("Application's ready");
|
|
90
|
-
this.windowManager.createMain(); // Your custom logic here to create a window
|
|
85
|
+
@Post('create')
|
|
86
|
+
create(req: Request) {
|
|
87
|
+
return { created: true, body: req.body };
|
|
91
88
|
}
|
|
92
89
|
}
|
|
93
90
|
```
|
|
94
91
|
|
|
92
|
+
### 3. Create the application service
|
|
93
|
+
|
|
95
94
|
```ts
|
|
96
|
-
//
|
|
95
|
+
// app.service.ts
|
|
96
|
+
import { IApp, Injectable, WindowManager } from '@noxfly/noxus/main';
|
|
97
|
+
import path from 'path';
|
|
98
|
+
|
|
99
|
+
@Injectable({ lifetime: 'singleton', deps: [WindowManager] })
|
|
100
|
+
export class AppService implements IApp {
|
|
101
|
+
constructor(private wm: WindowManager) {}
|
|
102
|
+
|
|
103
|
+
async onReady() {
|
|
104
|
+
const win = await this.wm.createSplash({
|
|
105
|
+
webPreferences: {
|
|
106
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
win.loadFile('index.html');
|
|
111
|
+
}
|
|
97
112
|
|
|
98
|
-
|
|
113
|
+
async onActivated() {
|
|
114
|
+
if (this.wm.count === 0) await this.onReady();
|
|
115
|
+
}
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
})
|
|
105
|
-
export class AppModule {}
|
|
117
|
+
async dispose() {
|
|
118
|
+
// cleanup on app close
|
|
119
|
+
}
|
|
120
|
+
}
|
|
106
121
|
```
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
### 4. Bootstrap the application
|
|
109
124
|
|
|
110
125
|
```ts
|
|
111
|
-
// main
|
|
126
|
+
// main.ts
|
|
127
|
+
import { bootstrapApplication } from '@noxfly/noxus/main';
|
|
128
|
+
import { AppService } from './app.service';
|
|
112
129
|
|
|
113
|
-
|
|
130
|
+
const noxApp = await bootstrapApplication();
|
|
114
131
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
controllers
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
noxApp
|
|
133
|
+
.configure(AppService)
|
|
134
|
+
.lazy('users', () => import('./controllers/user.controller.js'))
|
|
135
|
+
.lazy('orders', () => import('./controllers/order.controller.js'))
|
|
136
|
+
.start();
|
|
120
137
|
```
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
// main/modules/users/users.service.ts
|
|
139
|
+
---
|
|
124
140
|
|
|
125
|
-
|
|
141
|
+
## Dependency Injection
|
|
126
142
|
|
|
127
|
-
|
|
128
|
-
export class UsersService {
|
|
129
|
-
public async findAll(): Promise<User[]> {
|
|
130
|
-
// ...
|
|
131
|
-
}
|
|
143
|
+
### `@Injectable`
|
|
132
144
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
```ts
|
|
146
|
+
@Injectable({ lifetime: 'singleton', deps: [RepoA, RepoB] })
|
|
147
|
+
class MyService {
|
|
148
|
+
constructor(private a: RepoA, private b: RepoB) {}
|
|
136
149
|
}
|
|
137
150
|
```
|
|
138
151
|
|
|
139
|
-
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
| ---------- | --------------------------------------- | --------- | ---------------------------------- |
|
|
154
|
+
| `lifetime` | `'singleton' \| 'scope' \| 'transient'` | `'scope'` | Instance lifetime |
|
|
155
|
+
| `deps` | `TokenKey[]` | `[]` | Constructor dependencies, in order |
|
|
140
156
|
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
**Lifetimes:**
|
|
158
|
+
- `singleton` — one instance for the entire lifetime of the app
|
|
159
|
+
- `scope` — one instance per IPC request
|
|
160
|
+
- `transient` — a new instance on every resolution
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
import { UsersService } from './users.service.ts';
|
|
162
|
+
### `token()` — Non-class dependencies
|
|
146
163
|
|
|
147
|
-
|
|
148
|
-
export class UsersController {
|
|
149
|
-
constructor(
|
|
150
|
-
private readonly usersService: UsersService,
|
|
151
|
-
) {}
|
|
164
|
+
To inject values that are not classes (strings, interfaces, config objects):
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
166
|
+
```ts
|
|
167
|
+
// tokens.ts
|
|
168
|
+
import { token } from '@noxfly/noxus/main';
|
|
157
169
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
public getProfile(IRequest request, IResponse response): Promise<User | undefined> {
|
|
161
|
-
return await this.usersService.findOneById(request.params.id);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
170
|
+
export const DB_URL = token<string>('DB_URL');
|
|
171
|
+
export const APP_CONFIG = token<AppConfig>('APP_CONFIG');
|
|
164
172
|
```
|
|
165
173
|
|
|
166
|
-
Further upgrades might include new decorators like `@Param()`, `@Body()` etc... like Nest.js offers.
|
|
167
|
-
|
|
168
174
|
```ts
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@Injectable()
|
|
174
|
-
export class AuthGuard implements IGuard {
|
|
175
|
-
constructor(
|
|
176
|
-
private readonly authService: AuthService
|
|
177
|
-
) {}
|
|
178
|
-
|
|
179
|
-
public async canActivate(IRequest request): MaybeAsync<boolean> {
|
|
180
|
-
return this.authService.isAuthenticated();
|
|
181
|
-
}
|
|
175
|
+
// Declaring the dependency
|
|
176
|
+
@Injectable({ deps: [DB_URL, APP_CONFIG] })
|
|
177
|
+
class DbService {
|
|
178
|
+
constructor(private url: string, private config: AppConfig) {}
|
|
182
179
|
}
|
|
180
|
+
|
|
181
|
+
// Providing the value in bootstrapApplication
|
|
182
|
+
bootstrapApplication({
|
|
183
|
+
singletons: [
|
|
184
|
+
{ token: DB_URL, useValue: process.env.DATABASE_URL! },
|
|
185
|
+
{ token: APP_CONFIG, useValue: { debug: true } },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
183
188
|
```
|
|
184
189
|
|
|
185
|
-
|
|
190
|
+
### `inject()` — Manual resolution
|
|
186
191
|
|
|
187
|
-
|
|
192
|
+
```ts
|
|
193
|
+
import { inject } from '@noxfly/noxus/main';
|
|
188
194
|
|
|
195
|
+
const userService = inject(UserService);
|
|
196
|
+
```
|
|
189
197
|
|
|
190
|
-
|
|
198
|
+
Useful outside a constructor — in callbacks, factories, etc.
|
|
191
199
|
|
|
192
|
-
|
|
200
|
+
### `forwardRef()` — Circular dependencies
|
|
193
201
|
|
|
194
202
|
```ts
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
import { exposeNoxusBridge } from '@noxfly/noxus';
|
|
203
|
+
import { forwardRef } from '@noxfly/noxus/main';
|
|
198
204
|
|
|
199
|
-
|
|
205
|
+
@Injectable({ deps: [forwardRef(() => ServiceB)] })
|
|
206
|
+
class ServiceA {
|
|
207
|
+
constructor(private b: ServiceB) {}
|
|
208
|
+
}
|
|
200
209
|
```
|
|
201
210
|
|
|
202
|
-
|
|
211
|
+
---
|
|
203
212
|
|
|
204
|
-
|
|
213
|
+
## Routing
|
|
205
214
|
|
|
215
|
+
### Available HTTP methods
|
|
206
216
|
|
|
207
|
-
|
|
217
|
+
```ts
|
|
218
|
+
import { Get, Post, Put, Patch, Delete } from '@noxfly/noxus/main';
|
|
208
219
|
|
|
209
|
-
|
|
220
|
+
@Controller({ path: 'products', deps: [ProductService] })
|
|
221
|
+
class ProductController {
|
|
222
|
+
constructor(private svc: ProductService) {}
|
|
210
223
|
|
|
211
|
-
|
|
224
|
+
@Get('list') list(req: Request) { ... }
|
|
225
|
+
@Post('create') create(req: Request) { ... }
|
|
226
|
+
@Put(':id') replace(req: Request) { ... }
|
|
227
|
+
@Patch(':id') update(req: Request) { ... }
|
|
228
|
+
@Delete(':id') remove(req: Request) { ... }
|
|
229
|
+
}
|
|
230
|
+
```
|
|
212
231
|
|
|
213
|
-
|
|
232
|
+
### Route parameters
|
|
214
233
|
|
|
215
234
|
```ts
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
IBatchRequestItem,
|
|
222
|
-
IBatchResponsePayload,
|
|
223
|
-
IRequest,
|
|
224
|
-
NoxRendererClient,
|
|
225
|
-
} from '@noxfly/noxus';
|
|
226
|
-
|
|
227
|
-
@Injectable({ providedIn: 'root' })
|
|
228
|
-
export class NoxusService extends NoxRendererClient {
|
|
229
|
-
public async init(): Promise<void> {
|
|
230
|
-
await this.setup();
|
|
231
|
-
}
|
|
235
|
+
@Get('category/:categoryId/product/:productId')
|
|
236
|
+
getProduct(req: Request) {
|
|
237
|
+
const { categoryId, productId } = req.params;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
return from(this.request<T, U>(request));
|
|
235
|
-
}
|
|
241
|
+
### Request body
|
|
236
242
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
```ts
|
|
244
|
+
@Post('create')
|
|
245
|
+
create(req: Request) {
|
|
246
|
+
const { name, price } = req.body as { name: string; price: number };
|
|
240
247
|
}
|
|
248
|
+
```
|
|
241
249
|
|
|
242
|
-
|
|
243
|
-
await noxusService.init();
|
|
250
|
+
### Response
|
|
244
251
|
|
|
245
|
-
|
|
246
|
-
const subscription = noxusService.events.subscribe('users.updated', (payload) => {
|
|
247
|
-
console.log('Users updated:', payload);
|
|
248
|
-
});
|
|
249
|
-
```
|
|
252
|
+
The value returned by the handler is automatically placed in `response.body`. To control the status code:
|
|
250
253
|
|
|
251
|
-
|
|
254
|
+
```ts
|
|
255
|
+
@Post('create')
|
|
256
|
+
create(req: Request, res: IResponse) {
|
|
257
|
+
res.status = 201;
|
|
258
|
+
return { id: 42 };
|
|
259
|
+
}
|
|
260
|
+
```
|
|
252
261
|
|
|
253
|
-
|
|
262
|
+
---
|
|
254
263
|
|
|
264
|
+
## Lazy loading
|
|
255
265
|
|
|
256
|
-
|
|
266
|
+
This is the core mechanism for keeping startup fast. A lazy controller is never imported until an IPC request targets its prefix.
|
|
257
267
|
|
|
258
|
-
|
|
268
|
+
```ts
|
|
269
|
+
noxApp
|
|
270
|
+
.lazy('users', () => import('./modules/users/users.controller.js'))
|
|
271
|
+
.lazy('orders', () => import('./modules/orders/orders.controller.js'))
|
|
272
|
+
.lazy('printing', () => import('./modules/printing/printing.controller.js'))
|
|
273
|
+
.start();
|
|
274
|
+
```
|
|
259
275
|
|
|
260
|
-
|
|
276
|
+
> **Important:** the `import()` argument must not statically reference heavy modules. If `users.controller.ts` imports `applicationinsights` at the top of the file, the library will be loaded on the first `users/*` request — not at startup.
|
|
261
277
|
|
|
262
|
-
|
|
278
|
+
### Eager loading (`eagerLoad`)
|
|
263
279
|
|
|
264
|
-
|
|
280
|
+
For modules whose services are needed before `onReady()`:
|
|
265
281
|
|
|
266
|
-
You throw it as follow :
|
|
267
282
|
```ts
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
283
|
+
bootstrapApplication({
|
|
284
|
+
eagerLoad: [
|
|
285
|
+
() => import('./modules/auth/auth.controller.js'),
|
|
286
|
+
],
|
|
287
|
+
});
|
|
272
288
|
```
|
|
273
289
|
|
|
274
|
-
|
|
275
|
-
| ----------- | -------------------------------------- |
|
|
276
|
-
| 400 | BadRequestException |
|
|
277
|
-
| 401 | UnauthorizedException |
|
|
278
|
-
| 402 | PaymentRequiredException |
|
|
279
|
-
| 403 | ForbiddenException |
|
|
280
|
-
| 404 | NotFoundException |
|
|
281
|
-
| 405 | MethodNotAllowedException |
|
|
282
|
-
| 406 | NotAcceptableException |
|
|
283
|
-
| 408 | RequestTimeoutException |
|
|
284
|
-
| 409 | ConflictException |
|
|
285
|
-
| 426 | UpgradeRequiredException |
|
|
286
|
-
| 429 | TooManyRequestsException |
|
|
287
|
-
| 500 | InternalServerException |
|
|
288
|
-
| 501 | NotImplementedException |
|
|
289
|
-
| 502 | BadGatewayException |
|
|
290
|
-
| 503 | ServiceUnavailableException |
|
|
291
|
-
| 504 | GatewayTimeoutException |
|
|
292
|
-
| 505 | HttpVersionNotSupportedException |
|
|
293
|
-
| 506 | VariantAlsoNegotiatesException |
|
|
294
|
-
| 507 | InsufficientStorageException |
|
|
295
|
-
| 508 | LoopDetectedException |
|
|
296
|
-
| 510 | NotExtendedException |
|
|
297
|
-
| 511 | NetworkAuthenticationRequiredException |
|
|
298
|
-
| 599 | NetworkConnectTimeoutException |
|
|
290
|
+
### Manual loading from NoxApp
|
|
299
291
|
|
|
292
|
+
```ts
|
|
293
|
+
await noxApp.load([
|
|
294
|
+
() => import('./modules/reporting/reporting.controller.js'),
|
|
295
|
+
]);
|
|
296
|
+
```
|
|
300
297
|
|
|
301
|
-
|
|
298
|
+
---
|
|
302
299
|
|
|
303
|
-
|
|
300
|
+
## Guards
|
|
304
301
|
|
|
305
|
-
|
|
302
|
+
A guard is a plain function that decides whether a request can reach its handler.
|
|
306
303
|
|
|
307
304
|
```ts
|
|
308
|
-
|
|
309
|
-
import {
|
|
305
|
+
// guards/auth.guard.ts
|
|
306
|
+
import { Guard } from '@noxfly/noxus/main';
|
|
310
307
|
|
|
311
|
-
const
|
|
308
|
+
export const authGuard: Guard = async (req) => {
|
|
309
|
+
return req.body?.token === 'secret'; // your auth logic
|
|
310
|
+
};
|
|
312
311
|
```
|
|
313
312
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
313
|
+
**On an entire controller:**
|
|
314
|
+
```ts
|
|
315
|
+
@Controller({ path: 'admin', deps: [AdminService], guards: [authGuard] })
|
|
316
|
+
class AdminController { ... }
|
|
317
|
+
```
|
|
319
318
|
|
|
319
|
+
**On a specific route:**
|
|
320
320
|
```ts
|
|
321
|
-
|
|
321
|
+
@Delete(':id', { guards: [authGuard, adminGuard] })
|
|
322
|
+
remove(req: Request) { ... }
|
|
323
|
+
```
|
|
322
324
|
|
|
323
|
-
|
|
324
|
-
class ServiceA {
|
|
325
|
-
constructor(
|
|
326
|
-
@Inject(forwardRef(() => ServiceB))
|
|
327
|
-
private readonly serviceB: ServiceB
|
|
328
|
-
) {}
|
|
329
|
-
}
|
|
325
|
+
Controller guards and route guards are **cumulative** — both run, in the given order.
|
|
330
326
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
private readonly serviceA: ServiceA
|
|
335
|
-
) {}
|
|
336
|
-
}
|
|
337
|
-
```
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Middlewares
|
|
338
330
|
|
|
339
|
-
|
|
331
|
+
A middleware is a plain function that runs before guards.
|
|
340
332
|
|
|
341
333
|
```ts
|
|
342
|
-
|
|
334
|
+
// middlewares/log.middleware.ts
|
|
335
|
+
import { Middleware } from '@noxfly/noxus/main';
|
|
336
|
+
|
|
337
|
+
export const logMiddleware: Middleware = async (req, res, next) => {
|
|
338
|
+
console.log(`→ ${req.method} ${req.path}`);
|
|
339
|
+
await next();
|
|
340
|
+
console.log(`← ${res.status}`);
|
|
341
|
+
};
|
|
342
|
+
```
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
344
|
+
**Global (all routes):**
|
|
345
|
+
```ts
|
|
346
|
+
noxApp.use(logMiddleware);
|
|
347
|
+
```
|
|
349
348
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
349
|
+
**On a controller:**
|
|
350
|
+
```ts
|
|
351
|
+
@Controller({ path: 'users', deps: [...], middlewares: [logMiddleware] })
|
|
352
|
+
class UserController { ... }
|
|
354
353
|
```
|
|
355
354
|
|
|
356
|
-
|
|
355
|
+
**On a route:**
|
|
356
|
+
```ts
|
|
357
|
+
@Post('upload', { middlewares: [fileSizeMiddleware] })
|
|
358
|
+
upload(req: Request) { ... }
|
|
359
|
+
```
|
|
357
360
|
|
|
358
|
-
|
|
361
|
+
**Execution order:** global middlewares → controller middlewares → route middlewares → guards → handler.
|
|
359
362
|
|
|
360
|
-
|
|
363
|
+
---
|
|
361
364
|
|
|
362
|
-
|
|
363
|
-
// renderer/middlewares.ts
|
|
365
|
+
## WindowManager
|
|
364
366
|
|
|
365
|
-
|
|
367
|
+
Injectable singleton service for managing `BrowserWindow` instances.
|
|
366
368
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
369
|
+
```ts
|
|
370
|
+
@Injectable({ lifetime: 'singleton', deps: [WindowManager] })
|
|
371
|
+
class AppService implements IApp {
|
|
372
|
+
constructor(private wm: WindowManager) {}
|
|
373
|
+
|
|
374
|
+
async onReady() {
|
|
375
|
+
// Creates a 600×600 window, animates it to full screen,
|
|
376
|
+
// then resolves the promise once the animation is complete.
|
|
377
|
+
// loadFile() is therefore always called at the correct size — no viewbox freeze.
|
|
378
|
+
const win = await this.wm.createSplash({
|
|
379
|
+
webPreferences: { preload: path.join(__dirname, 'preload.js') },
|
|
380
|
+
});
|
|
381
|
+
win.loadFile('index.html');
|
|
373
382
|
}
|
|
374
383
|
}
|
|
384
|
+
```
|
|
375
385
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
386
|
+
### Full API
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
// Creation
|
|
390
|
+
const win = await wm.createSplash(options); // animated main window
|
|
391
|
+
const win2 = await wm.create(config, isMain?); // custom window
|
|
392
|
+
|
|
393
|
+
// Access
|
|
394
|
+
wm.getMain() // main window
|
|
395
|
+
wm.getById(id) // by Electron id
|
|
396
|
+
wm.getAll() // all open windows
|
|
397
|
+
wm.count // number of open windows
|
|
398
|
+
|
|
399
|
+
// Actions
|
|
400
|
+
wm.close(id) // close a window
|
|
401
|
+
wm.closeAll() // close all windows
|
|
402
|
+
|
|
403
|
+
// Messaging
|
|
404
|
+
wm.send(id, 'channel', ...args) // send a message to one window
|
|
405
|
+
wm.broadcast('channel', ...args) // send to all windows
|
|
384
406
|
```
|
|
385
407
|
|
|
386
|
-
|
|
408
|
+
### `createSplash` vs `create`
|
|
387
409
|
|
|
388
|
-
|
|
410
|
+
| | `createSplash` | `create` |
|
|
411
|
+
| ------------ | ---------------------- | ----------------------------------- |
|
|
412
|
+
| Initial size | 600×600 centered | Whatever you define |
|
|
413
|
+
| Animation | Expands to work area | Optional (`expandToWorkArea: true`) |
|
|
414
|
+
| `show` | `true` immediately | `false` until `ready-to-show` |
|
|
415
|
+
| Use case | Main window at startup | Secondary windows |
|
|
389
416
|
|
|
390
|
-
|
|
417
|
+
---
|
|
391
418
|
|
|
392
|
-
|
|
393
|
-
const noxApp = bootstrapApplication(AppModule);
|
|
419
|
+
## External singletons
|
|
394
420
|
|
|
395
|
-
|
|
421
|
+
To inject values built outside the DI container (DB connection, third-party SDK):
|
|
396
422
|
|
|
397
|
-
|
|
398
|
-
|
|
423
|
+
```ts
|
|
424
|
+
// main.ts
|
|
425
|
+
import { MikroORM } from '@mikro-orm/core';
|
|
426
|
+
import { bootstrapApplication } from '@noxfly/noxus/main';
|
|
399
427
|
|
|
400
|
-
|
|
428
|
+
const orm = await MikroORM.init(ormConfig);
|
|
429
|
+
|
|
430
|
+
const noxApp = await bootstrapApplication({
|
|
431
|
+
singletons: [
|
|
432
|
+
{ token: MikroORM, useValue: orm },
|
|
433
|
+
],
|
|
434
|
+
});
|
|
401
435
|
```
|
|
402
436
|
|
|
403
|
-
|
|
437
|
+
These values are then available via injection in any service:
|
|
404
438
|
|
|
405
439
|
```ts
|
|
406
|
-
@
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
@Get('all')
|
|
410
|
-
@UseMiddlewares([MiddlewareA, MiddlewareB])
|
|
411
|
-
public getAll(): Promise<void> {
|
|
412
|
-
// ...
|
|
413
|
-
}
|
|
440
|
+
@Injectable({ lifetime: 'singleton', deps: [MikroORM] })
|
|
441
|
+
class UserRepository {
|
|
442
|
+
constructor(private orm: MikroORM) {}
|
|
414
443
|
}
|
|
415
444
|
```
|
|
416
445
|
|
|
417
|
-
|
|
446
|
+
---
|
|
418
447
|
|
|
419
|
-
|
|
448
|
+
## Renderer side
|
|
420
449
|
|
|
421
|
-
|
|
450
|
+
### Preload
|
|
422
451
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
3. Use Middleware C for controller
|
|
427
|
-
4. Use Middleware D for action
|
|
428
|
-
5. Use AuthGuard on the controller
|
|
429
|
-
6. Use RoleGuard on the action
|
|
430
|
-
|
|
431
|
-
Then the executing pipeline will be as follow :
|
|
452
|
+
```ts
|
|
453
|
+
// preload.ts
|
|
454
|
+
import { createPreloadBridge } from '@noxfly/noxus';
|
|
432
455
|
|
|
433
|
-
|
|
434
|
-
A -> B -> C -> D -> AuthGuard -> RoleGuard -> [action] -> D -> C -> B -> A.
|
|
456
|
+
createPreloadBridge(); // exposes window.__noxus__ to the renderer
|
|
435
457
|
```
|
|
436
458
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
459
|
+
### IPC client
|
|
441
460
|
|
|
461
|
+
```ts
|
|
462
|
+
// In the renderer (Angular, React, Vue, Vanilla...)
|
|
463
|
+
import { NoxusClient } from '@noxfly/noxus';
|
|
464
|
+
|
|
465
|
+
const client = new NoxusClient();
|
|
466
|
+
await client.connect();
|
|
467
|
+
|
|
468
|
+
// Requests
|
|
469
|
+
const users = await client.get<User[]>('users/list');
|
|
470
|
+
const user = await client.get<User>('users/42');
|
|
471
|
+
await client.post('users/create', { name: 'Bob' });
|
|
472
|
+
await client.put('users/42', { name: 'Bob Updated' });
|
|
473
|
+
await client.delete('users/42');
|
|
474
|
+
```
|
|
442
475
|
|
|
443
|
-
|
|
476
|
+
### Push events (main → renderer)
|
|
444
477
|
|
|
445
|
-
|
|
478
|
+
On the main side, via `NoxSocket`:
|
|
446
479
|
|
|
447
480
|
```ts
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
@Controller('users')
|
|
452
|
-
export class UsersController {
|
|
453
|
-
constructor(private readonly socket: NoxSocket) {}
|
|
481
|
+
@Injectable({ lifetime: 'singleton', deps: [NoxSocket] })
|
|
482
|
+
class NotificationService {
|
|
483
|
+
constructor(private socket: NoxSocket) {}
|
|
454
484
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
485
|
+
notifyAll(message: string) {
|
|
486
|
+
this.socket.emit('notification', { message });
|
|
487
|
+
}
|
|
458
488
|
|
|
459
|
-
|
|
460
|
-
|
|
489
|
+
notifyOne(senderId: number, message: string) {
|
|
490
|
+
this.socket.emitToRenderer(senderId, 'notification', { message });
|
|
461
491
|
}
|
|
462
492
|
}
|
|
463
493
|
```
|
|
464
494
|
|
|
465
|
-
On the renderer side
|
|
495
|
+
On the renderer side:
|
|
466
496
|
|
|
467
497
|
```ts
|
|
468
|
-
|
|
498
|
+
client.on('notification', (payload) => {
|
|
499
|
+
console.log(payload.message);
|
|
500
|
+
});
|
|
501
|
+
```
|
|
469
502
|
|
|
470
|
-
|
|
503
|
+
---
|
|
471
504
|
|
|
472
|
-
|
|
473
|
-
// ... after the MessagePort is ready
|
|
474
|
-
this.port.onmessage = (event) => {
|
|
475
|
-
if(this.events.tryDispatchFromMessageEvent(event)) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
505
|
+
## Batch requests
|
|
478
506
|
|
|
479
|
-
|
|
480
|
-
};
|
|
481
|
-
}
|
|
507
|
+
Multiple IPC requests in a single round-trip:
|
|
482
508
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
public teardown(): void {
|
|
490
|
-
this.events.clear();
|
|
491
|
-
}
|
|
509
|
+
```ts
|
|
510
|
+
const results = await client.batch([
|
|
511
|
+
{ method: 'GET', path: 'users/list' },
|
|
512
|
+
{ method: 'GET', path: 'products/list' },
|
|
513
|
+
{ method: 'POST', path: 'orders/create', body: { ... } },
|
|
514
|
+
]);
|
|
492
515
|
```
|
|
493
516
|
|
|
517
|
+
---
|
|
494
518
|
|
|
519
|
+
## Exceptions
|
|
495
520
|
|
|
521
|
+
Noxus provides an HTTP exception hierarchy to throw from handlers:
|
|
496
522
|
|
|
497
|
-
|
|
523
|
+
```ts
|
|
524
|
+
import {
|
|
525
|
+
BadRequestException, // 400
|
|
526
|
+
UnauthorizedException, // 401
|
|
527
|
+
ForbiddenException, // 403
|
|
528
|
+
NotFoundException, // 404
|
|
529
|
+
ConflictException, // 409
|
|
530
|
+
InternalServerException, // 500
|
|
531
|
+
// ... and all other 4xx/5xx
|
|
532
|
+
} from '@noxfly/noxus/main';
|
|
533
|
+
|
|
534
|
+
@Get(':id')
|
|
535
|
+
getOne(req: Request) {
|
|
536
|
+
const user = this.svc.findById(parseInt(req.params['id']!));
|
|
537
|
+
if (!user) throw new NotFoundException(`User not found`);
|
|
538
|
+
return user;
|
|
539
|
+
}
|
|
540
|
+
```
|
|
498
541
|
|
|
499
|
-
|
|
500
|
-
1. `npm i`
|
|
501
|
-
1. Develop
|
|
502
|
-
1. Push changes (automatically builds)
|
|
503
|
-
1. Create a PR
|
|
542
|
+
The exception is automatically caught by the router and translated into a response with the correct HTTP status.
|
|
504
543
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Recommended project structure
|
|
547
|
+
|
|
548
|
+
```
|
|
549
|
+
src/
|
|
550
|
+
├── main.ts ← bootstrapApplication + lazy routes
|
|
551
|
+
├── app.service.ts ← implements IApp
|
|
552
|
+
├── modules/
|
|
553
|
+
│ ├── users/
|
|
554
|
+
│ │ ├── user.controller.ts
|
|
555
|
+
│ │ ├── user.service.ts
|
|
556
|
+
│ │ └── user.repository.ts
|
|
557
|
+
│ ├── orders/
|
|
558
|
+
│ │ ├── order.controller.ts
|
|
559
|
+
│ │ └── order.service.ts
|
|
560
|
+
│ └── printing/
|
|
561
|
+
│ ├── printing.controller.ts
|
|
562
|
+
│ └── printing.service.ts
|
|
563
|
+
├── guards/
|
|
564
|
+
│ └── auth.guard.ts
|
|
565
|
+
├── middlewares/
|
|
566
|
+
│ └── log.middleware.ts
|
|
567
|
+
└── tokens.ts ← shared named tokens
|
|
568
|
+
```
|
|
508
569
|
|
|
570
|
+
Each `module/` folder is **self-contained** — the controller imports its own services directly, with no central declaration. `main.ts` only knows the lazy loading paths.
|