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

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.
Files changed (49) hide show
  1. package/dist/child.js.map +1 -0
  2. package/dist/child.mjs.map +1 -0
  3. package/dist/main.d.mts +4 -4
  4. package/dist/main.d.ts +4 -4
  5. package/dist/main.js.map +1 -0
  6. package/dist/main.mjs.map +1 -0
  7. package/dist/preload.js.map +1 -0
  8. package/dist/preload.mjs.map +1 -0
  9. package/dist/renderer.d.mts +4 -4
  10. package/dist/renderer.d.ts +4 -4
  11. package/dist/renderer.js.map +1 -0
  12. package/dist/renderer.mjs.map +1 -0
  13. package/package.json +10 -9
  14. package/.editorconfig +0 -16
  15. package/.github/copilot-instructions.md +0 -128
  16. package/.vscode/settings.json +0 -3
  17. package/AGENTS.md +0 -5
  18. package/eslint.config.js +0 -109
  19. package/scripts/postbuild.js +0 -31
  20. package/src/DI/app-injector.ts +0 -173
  21. package/src/DI/injector-explorer.ts +0 -201
  22. package/src/DI/token.ts +0 -53
  23. package/src/decorators/controller.decorator.ts +0 -58
  24. package/src/decorators/guards.decorator.ts +0 -15
  25. package/src/decorators/injectable.decorator.ts +0 -81
  26. package/src/decorators/method.decorator.ts +0 -66
  27. package/src/decorators/middleware.decorator.ts +0 -15
  28. package/src/index.ts +0 -10
  29. package/src/internal/app.ts +0 -219
  30. package/src/internal/bootstrap.ts +0 -141
  31. package/src/internal/exceptions.ts +0 -57
  32. package/src/internal/preload-bridge.ts +0 -75
  33. package/src/internal/renderer-client.ts +0 -374
  34. package/src/internal/renderer-events.ts +0 -110
  35. package/src/internal/request.ts +0 -102
  36. package/src/internal/router.ts +0 -365
  37. package/src/internal/routes.ts +0 -142
  38. package/src/internal/socket.ts +0 -75
  39. package/src/main.ts +0 -26
  40. package/src/non-electron-process.ts +0 -22
  41. package/src/preload.ts +0 -10
  42. package/src/renderer.ts +0 -13
  43. package/src/utils/forward-ref.ts +0 -31
  44. package/src/utils/logger.ts +0 -430
  45. package/src/utils/radix-tree.ts +0 -243
  46. package/src/utils/types.ts +0 -21
  47. package/src/window/window-manager.ts +0 -302
  48. package/tsconfig.json +0 -29
  49. package/tsup.config.ts +0 -50
@@ -1,219 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { app, BrowserWindow, ipcMain, MessageChannelMain } from 'electron/main';
8
- import { Guard } from "../decorators/guards.decorator";
9
- import { Injectable } from '../decorators/injectable.decorator';
10
- import { Middleware } from '../decorators/middleware.decorator';
11
- import { inject } from '../DI/app-injector';
12
- import { InjectorExplorer } from '../DI/injector-explorer';
13
- import { Logger } from '../utils/logger';
14
- import { Type } from '../utils/types';
15
- import { WindowManager } from '../window/window-manager';
16
- import { IResponse, Request } from './request';
17
- import { Router } from './router';
18
- import { NoxSocket } from './socket';
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
- * }
41
- */
42
- export interface IApp {
43
- dispose(): Promise<void>;
44
- onReady(): Promise<void>;
45
- onActivated(): Promise<void>;
46
- }
47
-
48
- @Injectable({ lifetime: 'singleton', deps: [Router, NoxSocket, WindowManager] })
49
- export class NoxApp {
50
- private appService: IApp | undefined;
51
-
52
- constructor(
53
- private readonly router: Router,
54
- private readonly socket: NoxSocket,
55
- public readonly windowManager: WindowManager,
56
- ) {}
57
-
58
- // -------------------------------------------------------------------------
59
- // Initialisation
60
- // -------------------------------------------------------------------------
61
-
62
- public async init(): Promise<this> {
63
- ipcMain.on('gimme-my-port', this.giveTheRendererAPort.bind(this));
64
- app.once('activate', this.onAppActivated.bind(this));
65
- app.once('window-all-closed', this.onAllWindowsClosed.bind(this));
66
- console.log('');
67
- return this;
68
- }
69
-
70
- // -------------------------------------------------------------------------
71
- // Public API
72
- // -------------------------------------------------------------------------
73
-
74
- /**
75
- * Registers a lazy route. The file behind this prefix is dynamically
76
- * imported on the first IPC request that targets it.
77
- *
78
- * The import function should NOT statically reference heavy modules —
79
- * the whole point is to defer their loading.
80
- *
81
- * @example
82
- * noxApp.lazy('auth', () => import('./modules/auth/auth.controller.js'));
83
- * noxApp.lazy('reporting', () => import('./modules/reporting/index.js'));
84
- */
85
- public lazy(
86
- pathPrefix: string,
87
- load: () => Promise<unknown>,
88
- guards: Guard[] = [],
89
- middlewares: Middleware[] = [],
90
- ): this {
91
- this.router.registerLazyRoute(pathPrefix, load, guards, middlewares);
92
- return this;
93
- }
94
-
95
- /**
96
- * Eagerly loads a set of modules (controllers + services) before start().
97
- * Use this for modules that provide services needed by your IApp.onReady().
98
- *
99
- * All imports run in parallel; DI is flushed with the two-phase guarantee.
100
- */
101
- public async load(importFns: Array<() => Promise<unknown>>): Promise<this> {
102
- InjectorExplorer.beginAccumulate();
103
- await Promise.all(importFns.map((fn) => fn()));
104
- await InjectorExplorer.flushAccumulated();
105
- return this;
106
- }
107
-
108
- /**
109
- * Registers a global middleware applied to every route.
110
- */
111
- public use(middleware: Middleware): this {
112
- this.router.defineRootMiddleware(middleware);
113
- return this;
114
- }
115
-
116
- /**
117
- * Sets the application service (implements IApp) that receives lifecycle events.
118
- * @param appClass - Class decorated with @Injectable that implements IApp.
119
- */
120
- public configure(appClass: Type<IApp>): this {
121
- this.appService = inject(appClass);
122
- return this;
123
- }
124
-
125
- /**
126
- * Calls IApp.onReady(). Should be called after configure() and any lazy()
127
- * registrations are set up.
128
- */
129
- public start(): this {
130
- this.appService?.onReady();
131
- return this;
132
- }
133
-
134
- // -------------------------------------------------------------------------
135
- // IPC
136
- // -------------------------------------------------------------------------
137
-
138
- private readonly onRendererMessage = async (event: Electron.MessageEvent): Promise<void> => {
139
- const { senderId, requestId, path, method, body, query }: import('./request').IRequest = event.data;
140
- const channels = this.socket.get(senderId);
141
-
142
- if (!channels) {
143
- Logger.error(`No message channel found for sender ID: ${senderId}`);
144
- return;
145
- }
146
-
147
- try {
148
- const request = new Request(event, senderId, requestId, method, path, body, query);
149
- const response = await this.router.handle(request);
150
- channels.request.port1.postMessage(response);
151
- }
152
- catch (err: unknown) {
153
- const response: IResponse = {
154
- requestId,
155
- status: 500,
156
- body: null,
157
- error: err instanceof Error ? err.message : 'Internal Server Error',
158
- };
159
- channels.request.port1.postMessage(response);
160
- }
161
- };
162
-
163
- private giveTheRendererAPort(event: Electron.IpcMainInvokeEvent): void {
164
- const senderId = event.sender.id;
165
-
166
- if (this.socket.get(senderId)) {
167
- this.shutdownChannel(senderId);
168
- }
169
-
170
- const requestChannel = new MessageChannelMain();
171
- const socketChannel = new MessageChannelMain();
172
-
173
- requestChannel.port1.on('message', this.onRendererMessage);
174
- requestChannel.port1.start();
175
- socketChannel.port1.start();
176
-
177
- event.sender.once('destroyed', () => this.shutdownChannel(senderId));
178
-
179
- this.socket.register(senderId, requestChannel, socketChannel);
180
- event.sender.postMessage('port', { senderId }, [requestChannel.port2, socketChannel.port2]);
181
- }
182
-
183
- // -------------------------------------------------------------------------
184
- // Lifecycle
185
- // -------------------------------------------------------------------------
186
-
187
- private onAppActivated(): void {
188
- if (process.platform === 'darwin' && BrowserWindow.getAllWindows().length === 0) {
189
- this.appService?.onActivated();
190
- }
191
- }
192
-
193
- private async onAllWindowsClosed(): Promise<void> {
194
- for (const senderId of this.socket.getSenderIds()) {
195
- this.shutdownChannel(senderId);
196
- }
197
-
198
- Logger.info('All windows closed, shutting down application...');
199
- await this.appService?.dispose();
200
-
201
- if (process.platform !== 'darwin') app.quit();
202
- }
203
-
204
- private shutdownChannel(channelSenderId: number): void {
205
- const channels = this.socket.get(channelSenderId);
206
-
207
- if (!channels) {
208
- return;
209
- }
210
-
211
- channels.request.port1.off('message', this.onRendererMessage);
212
- channels.request.port1.close();
213
- channels.request.port2.close();
214
- channels.socket.port1.close();
215
- channels.socket.port2.close();
216
-
217
- this.socket.unregister(channelSenderId);
218
- }
219
- }
@@ -1,141 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { app } from 'electron/main';
8
- import { inject, RootInjector } from '../DI/app-injector';
9
- import { InjectorExplorer } from '../DI/injector-explorer';
10
- import { TokenKey } from '../DI/token';
11
- import { Logger } from '../utils/logger';
12
- import { NoxApp } from './app';
13
- import { RouteDefinition } from "./routes";
14
- import { Router } from './router';
15
-
16
- /**
17
- * A singleton value override: provides an already-constructed instance
18
- * for a given token, bypassing the DI factory.
19
- *
20
- * Useful for injecting external singletons (e.g. a database connection,
21
- * a logger already configured, a third-party SDK wrapper) that cannot
22
- * or should not be constructed by the DI container.
23
- *
24
- * @example
25
- * { token: MikroORM, useValue: await MikroORM.init(config) }
26
- * { token: DB_URL, useValue: process.env.DATABASE_URL }
27
- */
28
- export interface SingletonOverride<T = unknown> {
29
- token: TokenKey<T>;
30
- useValue: T;
31
- }
32
-
33
- /**
34
- * Configuration object for bootstrapApplication.
35
- */
36
- export interface BootstrapConfig {
37
- /**
38
- * Application routing table, produced by defineRoutes().
39
- * All lazy routes are registered before the app starts.
40
- */
41
- routes?: RouteDefinition[];
42
-
43
- /**
44
- * Pre-built singleton instances to inject into the DI container
45
- * before the application starts.
46
- *
47
- * This replaces the v2 module/provider declaration pattern for
48
- * external singletons.
49
- *
50
- * @example
51
- * singletons: [
52
- * { token: MikroORM, useValue: await MikroORM.init(ormConfig) },
53
- * { token: DB_URL, useValue: process.env.DATABASE_URL! },
54
- * ]
55
- */
56
- singletons?: SingletonOverride[];
57
-
58
- /**
59
- * Controllers and services to eagerly load before NoxApp.start() is called.
60
- * Each entry is a dynamic import function — files are imported in parallel.
61
- *
62
- * Use this only for things needed at startup (e.g. if your IApp service
63
- * depends on a service in an otherwise lazy module).
64
- *
65
- * Everything else should be registered via noxApp.lazy().
66
- *
67
- * @example
68
- * eagerLoad: [
69
- * () => import('./modules/auth/auth.controller.js'),
70
- * ]
71
- */
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[];
83
- }
84
-
85
- /**
86
- * Bootstraps the Noxus application.
87
- */
88
- export async function bootstrapApplication(config: BootstrapConfig = {}): Promise<NoxApp> {
89
- await app.whenReady();
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
-
102
- // Build override map for the DI flush phase
103
- const overrides = new Map<TokenKey, unknown>();
104
-
105
- for (const { token, useValue } of config.singletons ?? []) {
106
- overrides.set(token, useValue);
107
- // Pre-register the binding so the injector knows the token exists
108
- RootInjector.singletons.set(token as any, useValue);
109
- }
110
-
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
-
119
- InjectorExplorer.processPending(overrides);
120
-
121
- // Resolve core framework singletons
122
- const noxApp = inject(NoxApp);
123
-
124
- // Register routes from the routing table
125
- if (config.routes?.length) {
126
- for (const route of config.routes) {
127
- if (route.load) {
128
- noxApp.lazy(route.path, route.load, route.guards, route.middlewares);
129
- }
130
- }
131
- }
132
-
133
- // Eagerly load optional modules
134
- if (config.eagerLoad?.length) {
135
- await noxApp.load(config.eagerLoad);
136
- }
137
-
138
- await noxApp.init();
139
-
140
- return noxApp;
141
- }
@@ -1,57 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- export class ResponseException extends Error {
8
- public readonly status: number = 0;
9
-
10
- constructor(message?: string);
11
- constructor(statusCode?: number, message?: string);
12
- constructor(statusOrMessage?: number | string, message?: string) {
13
- let statusCode: number | undefined;
14
-
15
- if(typeof statusOrMessage === 'number') {
16
- statusCode = statusOrMessage;
17
- }
18
- else if(typeof statusOrMessage === 'string') {
19
- message = statusOrMessage;
20
- }
21
-
22
- super(message ?? "");
23
-
24
- if(statusCode !== undefined) {
25
- this.status = statusCode;
26
- }
27
-
28
- this.name = this.constructor.name
29
- .replace(/([A-Z])/g, ' $1');
30
- }
31
- }
32
-
33
- // 4XX
34
- export class BadRequestException extends ResponseException { public override readonly status = 400; }
35
- export class UnauthorizedException extends ResponseException { public override readonly status = 401; }
36
- export class PaymentRequiredException extends ResponseException { public override readonly status = 402; }
37
- export class ForbiddenException extends ResponseException { public override readonly status = 403; }
38
- export class NotFoundException extends ResponseException { public override readonly status = 404; }
39
- export class MethodNotAllowedException extends ResponseException { public override readonly status = 405; }
40
- export class NotAcceptableException extends ResponseException { public override readonly status = 406; }
41
- export class RequestTimeoutException extends ResponseException { public override readonly status = 408; }
42
- export class ConflictException extends ResponseException { public override readonly status = 409; }
43
- export class UpgradeRequiredException extends ResponseException { public override readonly status = 426; }
44
- export class TooManyRequestsException extends ResponseException { public override readonly status = 429; }
45
- // 5XX
46
- export class InternalServerException extends ResponseException { public override readonly status = 500; }
47
- export class NotImplementedException extends ResponseException { public override readonly status = 501; }
48
- export class BadGatewayException extends ResponseException { public override readonly status = 502; }
49
- export class ServiceUnavailableException extends ResponseException { public override readonly status = 503; }
50
- export class GatewayTimeoutException extends ResponseException { public override readonly status = 504; }
51
- export class HttpVersionNotSupportedException extends ResponseException { public override readonly status = 505; }
52
- export class VariantAlsoNegotiatesException extends ResponseException { public override readonly status = 506; }
53
- export class InsufficientStorageException extends ResponseException { public override readonly status = 507; }
54
- export class LoopDetectedException extends ResponseException { public override readonly status = 508; }
55
- export class NotExtendedException extends ResponseException { public override readonly status = 510; }
56
- export class NetworkAuthenticationRequiredException extends ResponseException { public override readonly status = 511; }
57
- export class NetworkConnectTimeoutException extends ResponseException { public override readonly status = 599; }
@@ -1,75 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { contextBridge, ipcRenderer } from 'electron/renderer';
8
- import type { IPortRequester } from './renderer-client';
9
-
10
- export interface NoxusPreloadAPI extends IPortRequester {}
11
-
12
- export interface NoxusPreloadOptions {
13
- exposeAs?: string;
14
- initMessageType?: string;
15
- requestChannel?: string;
16
- responseChannel?: string;
17
- targetWindow?: Window;
18
- }
19
-
20
- const DEFAULT_EXPOSE_NAME = 'noxus';
21
- const DEFAULT_INIT_EVENT = 'init-port';
22
- const DEFAULT_REQUEST_CHANNEL = 'gimme-my-port';
23
- const DEFAULT_RESPONSE_CHANNEL = 'port';
24
-
25
- /**
26
- * Exposes a minimal bridge in the isolated preload context so renderer processes
27
- * can request the two MessagePorts required by Noxus. The bridge forwards both
28
- * request/response and socket ports to the renderer via window.postMessage.
29
- */
30
- export function exposeNoxusBridge(options: NoxusPreloadOptions = {}): NoxusPreloadAPI {
31
- const {
32
- exposeAs = DEFAULT_EXPOSE_NAME,
33
- initMessageType = DEFAULT_INIT_EVENT,
34
- requestChannel = DEFAULT_REQUEST_CHANNEL,
35
- responseChannel = DEFAULT_RESPONSE_CHANNEL,
36
- targetWindow = window,
37
- } = options;
38
-
39
- const api: NoxusPreloadAPI = {
40
- requestPort: () => {
41
- ipcRenderer.send(requestChannel);
42
-
43
- ipcRenderer.once(responseChannel, (event, message: { senderId: number }) => {
44
- const ports = (event.ports ?? []).filter((port): port is MessagePort => port !== undefined);
45
-
46
- if(ports.length === 0) {
47
- console.error('[Noxus] No MessagePort received from main process.');
48
- return;
49
- }
50
-
51
- for(const port of ports) {
52
- try {
53
- port.start();
54
- }
55
- catch(error) {
56
- console.error('[Noxus] Failed to start MessagePort.', error);
57
- }
58
- }
59
-
60
- targetWindow.postMessage(
61
- {
62
- type: initMessageType,
63
- senderId: message?.senderId,
64
- },
65
- '*',
66
- ports,
67
- );
68
- });
69
- },
70
- };
71
-
72
- contextBridge.exposeInMainWorld(exposeAs, api);
73
-
74
- return api;
75
- }