@noxfly/noxus 3.0.0-dev.0 → 3.0.0-dev.10

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 (59) hide show
  1. package/README.md +132 -16
  2. package/dist/child.d.mts +118 -4
  3. package/dist/child.d.ts +118 -4
  4. package/dist/child.js +402 -859
  5. package/dist/child.js.map +1 -0
  6. package/dist/child.mjs +389 -847
  7. package/dist/child.mjs.map +1 -0
  8. package/dist/main.d.mts +517 -25
  9. package/dist/main.d.ts +517 -25
  10. package/dist/main.js +1088 -988
  11. package/dist/main.js.map +1 -0
  12. package/dist/main.mjs +983 -884
  13. package/dist/main.mjs.map +1 -0
  14. package/dist/preload.d.mts +28 -0
  15. package/dist/preload.d.ts +28 -0
  16. package/dist/preload.js +95 -0
  17. package/dist/preload.js.map +1 -0
  18. package/dist/preload.mjs +70 -0
  19. package/dist/preload.mjs.map +1 -0
  20. package/dist/renderer.d.mts +186 -23
  21. package/dist/renderer.d.ts +186 -23
  22. package/dist/renderer.js +170 -170
  23. package/dist/renderer.js.map +1 -0
  24. package/dist/renderer.mjs +159 -157
  25. package/dist/renderer.mjs.map +1 -0
  26. package/package.json +35 -21
  27. package/.editorconfig +0 -16
  28. package/.github/copilot-instructions.md +0 -32
  29. package/.vscode/settings.json +0 -3
  30. package/eslint.config.js +0 -109
  31. package/scripts/postbuild.js +0 -31
  32. package/src/DI/app-injector.ts +0 -151
  33. package/src/DI/injector-explorer.ts +0 -143
  34. package/src/DI/token.ts +0 -53
  35. package/src/app.ts +0 -217
  36. package/src/bootstrap.ts +0 -108
  37. package/src/decorators/controller.decorator.ts +0 -58
  38. package/src/decorators/guards.decorator.ts +0 -15
  39. package/src/decorators/injectable.decorator.ts +0 -81
  40. package/src/decorators/method.decorator.ts +0 -66
  41. package/src/decorators/middleware.decorator.ts +0 -15
  42. package/src/exceptions.ts +0 -57
  43. package/src/index.ts +0 -13
  44. package/src/main.ts +0 -26
  45. package/src/non-electron-process.ts +0 -22
  46. package/src/preload-bridge.ts +0 -75
  47. package/src/renderer-client.ts +0 -338
  48. package/src/renderer-events.ts +0 -110
  49. package/src/request.ts +0 -97
  50. package/src/router.ts +0 -353
  51. package/src/routes.ts +0 -78
  52. package/src/socket.ts +0 -73
  53. package/src/utils/forward-ref.ts +0 -31
  54. package/src/utils/logger.ts +0 -430
  55. package/src/utils/radix-tree.ts +0 -210
  56. package/src/utils/types.ts +0 -21
  57. package/src/window/window-manager.ts +0 -255
  58. package/tsconfig.json +0 -29
  59. package/tsup.config.ts +0 -34
package/src/router.ts DELETED
@@ -1,353 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { getControllerMetadata } from './decorators/controller.decorator';
8
- import { Guard } from './decorators/guards.decorator';
9
- import { Injectable } from './decorators/injectable.decorator';
10
- import { getRouteMetadata, isAtomicHttpMethod } from './decorators/method.decorator';
11
- import { Middleware, NextFunction } from './decorators/middleware.decorator';
12
- import { InjectorExplorer } from './DI/injector-explorer';
13
- import {
14
- BadRequestException,
15
- NotFoundException,
16
- ResponseException,
17
- UnauthorizedException
18
- } from './exceptions';
19
- import { IBatchRequestItem, IBatchRequestPayload, IBatchResponsePayload, IResponse, Request } from './request';
20
- import { Logger } from './utils/logger';
21
- import { RadixTree } from './utils/radix-tree';
22
- import { Type } from './utils/types';
23
-
24
- export interface ILazyRoute {
25
- load: () => Promise<unknown>;
26
- guards: Guard[];
27
- middlewares: Middleware[];
28
- loading: Promise<void> | null;
29
- loaded: boolean;
30
- }
31
-
32
- interface LazyRouteEntry {
33
- load: (() => Promise<unknown>) | null;
34
- guards: Guard[];
35
- middlewares: Middleware[];
36
- loading: Promise<void> | null;
37
- loaded: boolean;
38
- }
39
-
40
- export interface IRouteDefinition {
41
- method: string;
42
- path: string;
43
- controller: Type<unknown>;
44
- handler: string;
45
- guards: Guard[];
46
- middlewares: Middleware[];
47
- }
48
-
49
- export type ControllerAction = (request: Request, response: IResponse) => unknown;
50
-
51
- @Injectable({ lifetime: 'singleton' })
52
- export class Router {
53
- private readonly routes = new RadixTree<IRouteDefinition>();
54
- private readonly rootMiddlewares: Middleware[] = [];
55
- private readonly lazyRoutes = new Map<string, LazyRouteEntry>();
56
-
57
- // -------------------------------------------------------------------------
58
- // Registration
59
- // -------------------------------------------------------------------------
60
-
61
- public registerController(
62
- controllerClass: Type<unknown>,
63
- pathPrefix: string,
64
- routeGuards: Guard[] = [],
65
- routeMiddlewares: Middleware[] = [],
66
- ): this {
67
- const meta = getControllerMetadata(controllerClass);
68
-
69
- if (!meta) {
70
- throw new Error(`[Noxus] Missing @Controller decorator on ${controllerClass.name}`);
71
- }
72
-
73
- const routeMeta = getRouteMetadata(controllerClass);
74
-
75
- for (const def of routeMeta) {
76
- const fullPath = `${pathPrefix}/${def.path}`.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
77
-
78
- // Route-level guards/middlewares from defineRoutes() + action-level ones
79
- const guards = [...new Set([...routeGuards, ...def.guards])];
80
- const middlewares = [...new Set([...routeMiddlewares, ...def.middlewares])];
81
-
82
- const routeDef: IRouteDefinition = {
83
- method: def.method,
84
- path: fullPath,
85
- controller: controllerClass,
86
- handler: def.handler,
87
- guards,
88
- middlewares,
89
- };
90
-
91
- this.routes.insert(fullPath + '/' + def.method, routeDef);
92
-
93
- const guardInfo = guards.length ? `<${guards.map(g => g.name).join('|')}>` : '';
94
- Logger.log(`Mapped {${def.method} /${fullPath}}${guardInfo} route`);
95
- }
96
-
97
- const ctrlGuardInfo = routeGuards.length
98
- ? `<${routeGuards.map(g => g.name).join('|')}>`
99
- : '';
100
- Logger.log(`Mapped ${controllerClass.name}${ctrlGuardInfo} controller's routes`);
101
-
102
- return this;
103
- }
104
-
105
- public registerLazyRoute(
106
- pathPrefix: string,
107
- load: () => Promise<unknown>,
108
- guards: Guard[] = [],
109
- middlewares: Middleware[] = [],
110
- ): this {
111
- const normalized = pathPrefix.replace(/^\/+|\/+$/g, '');
112
- this.lazyRoutes.set(normalized, { load, guards, middlewares, loading: null, loaded: false });
113
- Logger.log(`Registered lazy route prefix {${normalized}}`);
114
- return this;
115
- }
116
-
117
- public defineRootMiddleware(middleware: Middleware): this {
118
- this.rootMiddlewares.push(middleware);
119
- return this;
120
- }
121
-
122
- // -------------------------------------------------------------------------
123
- // Request handling
124
- // -------------------------------------------------------------------------
125
-
126
- public async handle(request: Request): Promise<IResponse> {
127
- return request.method === 'BATCH'
128
- ? this.handleBatch(request)
129
- : this.handleAtomic(request);
130
- }
131
-
132
- private async handleAtomic(request: Request): Promise<IResponse> {
133
- Logger.comment(`> ${request.method} /${request.path}`);
134
- const t0 = performance.now();
135
-
136
- const response: IResponse = { requestId: request.id, status: 200, body: null };
137
- let isCritical = false;
138
-
139
- try {
140
- const routeDef = await this.findRoute(request);
141
- await this.resolveController(request, response, routeDef);
142
-
143
- if (response.status >= 400) throw new ResponseException(response.status, response.error);
144
- }
145
- catch (error) {
146
- this.fillErrorResponse(response, error, (c) => { isCritical = c; });
147
- }
148
- finally {
149
- this.logResponse(request, response, performance.now() - t0, isCritical);
150
- return response;
151
- }
152
- }
153
-
154
- private async handleBatch(request: Request): Promise<IResponse> {
155
- Logger.comment(`> ${request.method} /${request.path}`);
156
- const t0 = performance.now();
157
-
158
- const response: IResponse<IBatchResponsePayload> = {
159
- requestId: request.id,
160
- status: 200,
161
- body: { responses: [] },
162
- };
163
- let isCritical = false;
164
-
165
- try {
166
- const payload = this.normalizeBatchPayload(request.body);
167
- response.body!.responses = await Promise.all(
168
- payload.requests.map((item, i) => {
169
- const id = item.requestId ?? `${request.id}:${i}`;
170
- return this.handleAtomic(new Request(request.event, request.senderId, id, item.method, item.path, item.body));
171
- }),
172
- );
173
- }
174
- catch (error) {
175
- this.fillErrorResponse(response, error, (c) => { isCritical = c; });
176
- }
177
- finally {
178
- this.logResponse(request, response, performance.now() - t0, isCritical);
179
- return response;
180
- }
181
- }
182
-
183
- // -------------------------------------------------------------------------
184
- // Route resolution
185
- // -------------------------------------------------------------------------
186
-
187
- private tryFindRoute(request: Request): IRouteDefinition | undefined {
188
- const matched = this.routes.search(request.path);
189
- if (!matched?.node || matched.node.children.length === 0) return undefined;
190
- return matched.node.findExactChild(request.method)?.value;
191
- }
192
-
193
- private async findRoute(request: Request): Promise<IRouteDefinition> {
194
- const direct = this.tryFindRoute(request);
195
- if (direct) return direct;
196
-
197
- await this.tryLoadLazyRoute(request.path);
198
-
199
- const afterLazy = this.tryFindRoute(request);
200
- if (afterLazy) return afterLazy;
201
-
202
- throw new NotFoundException(`No route matches ${request.method} ${request.path}`);
203
- }
204
-
205
- private async tryLoadLazyRoute(requestPath: string): Promise<void> {
206
- const firstSegment = requestPath.replace(/^\/+/, '').split('/')[0] ?? '';
207
-
208
- for (const [prefix, entry] of this.lazyRoutes) {
209
- if (entry.loaded) continue;
210
- const normalized = requestPath.replace(/^\/+/, '');
211
- if (normalized === prefix || normalized.startsWith(prefix + '/') || firstSegment === prefix) {
212
- if (!entry.loading) entry.loading = this.loadLazyModule(prefix, entry);
213
- await entry.loading;
214
- return;
215
- }
216
- }
217
- }
218
-
219
- private async loadLazyModule(prefix: string, entry: LazyRouteEntry): Promise<void> {
220
- const t0 = performance.now();
221
- InjectorExplorer.beginAccumulate();
222
-
223
- await entry.load?.();
224
-
225
- entry.loading = null;
226
- entry.load = null;
227
-
228
- InjectorExplorer.flushAccumulated(entry.guards, entry.middlewares, prefix);
229
-
230
- entry.loaded = true;
231
-
232
- Logger.info(`Lazy-loaded module for prefix {${prefix}} in ${Math.round(performance.now() - t0)}ms`);
233
- }
234
-
235
- // -------------------------------------------------------------------------
236
- // Pipeline
237
- // -------------------------------------------------------------------------
238
-
239
- private async resolveController(request: Request, response: IResponse, routeDef: IRouteDefinition): Promise<void> {
240
- const instance = request.context.resolve(routeDef.controller);
241
- Object.assign(request.params, this.extractParams(request.path, routeDef.path));
242
- await this.runPipeline(request, response, routeDef, instance);
243
- }
244
-
245
- private async runPipeline(
246
- request: Request,
247
- response: IResponse,
248
- routeDef: IRouteDefinition,
249
- controllerInstance: unknown,
250
- ): Promise<void> {
251
- const middlewares = [...new Set([...this.rootMiddlewares, ...routeDef.middlewares])];
252
- const mwMax = middlewares.length - 1;
253
- const guardMax = mwMax + routeDef.guards.length;
254
- let index = -1;
255
-
256
- const dispatch = async (i: number): Promise<void> => {
257
- if (i <= index) throw new Error('next() called multiple times');
258
- index = i;
259
-
260
- if (i <= mwMax) {
261
- await this.runMiddleware(request, response, dispatch.bind(null, i + 1), middlewares[i]!);
262
- if (response.status >= 400) throw new ResponseException(response.status, response.error);
263
- return;
264
- }
265
-
266
- if (i <= guardMax) {
267
- await this.runGuard(request, routeDef.guards[i - middlewares.length]!);
268
- await dispatch(i + 1);
269
- return;
270
- }
271
-
272
- const action = (controllerInstance as Record<string, ControllerAction>)[routeDef.handler]!;
273
- response.body = await action.call(controllerInstance, request, response);
274
- if (response.body === undefined) response.body = {};
275
- };
276
-
277
- await dispatch(0);
278
- }
279
-
280
- private async runMiddleware(request: Request, response: IResponse, next: NextFunction, middleware: Middleware): Promise<void> {
281
- await middleware(request, response, next);
282
- }
283
-
284
- private async runGuard(request: Request, guard: Guard): Promise<void> {
285
- if (!await guard(request)) {
286
- throw new UnauthorizedException(`Unauthorized for ${request.method} ${request.path}`);
287
- }
288
- }
289
-
290
- // -------------------------------------------------------------------------
291
- // Utilities
292
- // -------------------------------------------------------------------------
293
-
294
- private extractParams(actual: string, template: string): Record<string, string> {
295
- const aParts = actual.split('/');
296
- const tParts = template.split('/');
297
- const params: Record<string, string> = {};
298
- tParts.forEach((part, i) => {
299
- if (part.startsWith(':')) params[part.slice(1)] = aParts[i] ?? '';
300
- });
301
- return params;
302
- }
303
-
304
- private normalizeBatchPayload(body: unknown): IBatchRequestPayload {
305
- if (body === null || typeof body !== 'object') {
306
- throw new BadRequestException('Batch payload must be an object containing a requests array.');
307
- }
308
- const { requests } = body as Partial<IBatchRequestPayload>;
309
- if (!Array.isArray(requests)) throw new BadRequestException('Batch payload must define a requests array.');
310
- return { requests: requests.map((e, i) => this.normalizeBatchItem(e, i)) };
311
- }
312
-
313
- private normalizeBatchItem(entry: unknown, index: number): IBatchRequestItem {
314
- if (entry === null || typeof entry !== 'object') throw new BadRequestException(`Batch request at index ${index} must be an object.`);
315
- const { requestId, path, method, body } = entry as Partial<IBatchRequestItem> & { method?: unknown };
316
- if (requestId !== undefined && typeof requestId !== 'string') throw new BadRequestException(`Batch request at index ${index} has an invalid requestId.`);
317
- if (typeof path !== 'string' || !path.length) throw new BadRequestException(`Batch request at index ${index} must define a non-empty path.`);
318
- if (typeof method !== 'string') throw new BadRequestException(`Batch request at index ${index} must define an HTTP method.`);
319
- const normalized = method.toUpperCase();
320
- if (!isAtomicHttpMethod(normalized)) throw new BadRequestException(`Batch request at index ${index} uses unsupported method ${method}.`);
321
- return { requestId, path, method: normalized, body };
322
- }
323
-
324
- private fillErrorResponse(response: IResponse, error: unknown, setCritical: (v: boolean) => void): void {
325
- response.body = undefined;
326
- if (error instanceof ResponseException) {
327
- response.status = error.status;
328
- response.error = error.message;
329
- response.stack = error.stack;
330
- } else if (error instanceof Error) {
331
- setCritical(true);
332
- response.status = 500;
333
- response.error = error.message || 'Internal Server Error';
334
- response.stack = error.stack;
335
- } else {
336
- setCritical(true);
337
- response.status = 500;
338
- response.error = 'Unknown error occurred';
339
- }
340
- }
341
-
342
- private logResponse(request: Request, response: IResponse, ms: number, isCritical: boolean): void {
343
- const msg = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(ms)}ms${Logger.colors.initial}`;
344
- if (response.status < 400) Logger.log(msg);
345
- else if (response.status < 500) Logger.warn(msg);
346
- else isCritical ? Logger.critical(msg) : Logger.error(msg);
347
-
348
- if (response.error) {
349
- isCritical ? Logger.critical(response.error) : Logger.error(response.error);
350
- if (response.stack) Logger.errorStack(response.stack);
351
- }
352
- }
353
- }
package/src/routes.ts DELETED
@@ -1,78 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { Guard } from './decorators/guards.decorator';
8
- import { Middleware } from './decorators/middleware.decorator';
9
-
10
- /**
11
- * A single route entry in the application routing table.
12
- */
13
- export interface RouteDefinition {
14
- /**
15
- * The path prefix for this route (e.g. 'users', 'orders').
16
- * All actions defined in the controller will be prefixed with this path.
17
- */
18
- path: string;
19
-
20
- /**
21
- * Dynamic import function returning the controller file.
22
- * The controller is loaded lazily on the first IPC request targeting this prefix.
23
- *
24
- * @example
25
- * load: () => import('./modules/users/users.controller')
26
- */
27
- load: () => Promise<unknown>;
28
-
29
- /**
30
- * Guards applied to every action in this controller.
31
- * Merged with action-level guards.
32
- */
33
- guards?: Guard[];
34
-
35
- /**
36
- * Middlewares applied to every action in this controller.
37
- * Merged with action-level middlewares.
38
- */
39
- middlewares?: Middleware[];
40
- }
41
-
42
- /**
43
- * Defines the application routing table.
44
- * Each entry maps a path prefix to a lazily-loaded controller.
45
- *
46
- * This is the single source of truth for routing — no path is declared
47
- * in @Controller(), preventing duplicate route prefixes across controllers.
48
- *
49
- * @example
50
- * export const routes = defineRoutes([
51
- * {
52
- * path: 'users',
53
- * load: () => import('./modules/users/users.controller'),
54
- * guards: [authGuard],
55
- * },
56
- * {
57
- * path: 'orders',
58
- * load: () => import('./modules/orders/orders.controller'),
59
- * guards: [authGuard],
60
- * middlewares: [logMiddleware],
61
- * },
62
- * ]);
63
- */
64
- export function defineRoutes(routes: RouteDefinition[]): RouteDefinition[] {
65
- const paths = routes.map(r => r.path.replace(/^\/+|\/+$/g, ''));
66
- const duplicates = paths.filter((p, i) => paths.indexOf(p) !== i);
67
-
68
- if (duplicates.length > 0) {
69
- throw new Error(
70
- `[Noxus] Duplicate route prefixes detected: ${[...new Set(duplicates)].map(d => `"${d}"`).join(', ')}`
71
- );
72
- }
73
-
74
- return routes.map(r => ({
75
- ...r,
76
- path: r.path.replace(/^\/+|\/+$/g, ''),
77
- }));
78
- }
package/src/socket.ts DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- /**
8
- * Centralizes MessagePort storage for renderer communication and handles
9
- * push-event delivery back to renderer processes.
10
- */
11
- import { Injectable } from './decorators/injectable.decorator';
12
- import { createRendererEventMessage } from './request';
13
- import { Logger } from './utils/logger';
14
-
15
- interface RendererChannels {
16
- request: Electron.MessageChannelMain;
17
- socket: Electron.MessageChannelMain;
18
- }
19
-
20
- @Injectable({ lifetime: 'singleton' })
21
- export class NoxSocket {
22
- private readonly channels = new Map<number, RendererChannels>();
23
-
24
- public register(senderId: number, requestChannel: Electron.MessageChannelMain, socketChannel: Electron.MessageChannelMain): void {
25
- this.channels.set(senderId, { request: requestChannel, socket: socketChannel });
26
- }
27
-
28
- public get(senderId: number): RendererChannels | undefined {
29
- return this.channels.get(senderId);
30
- }
31
-
32
- public unregister(senderId: number): void {
33
- this.channels.delete(senderId);
34
- }
35
-
36
- public getSenderIds(): number[] {
37
- return [...this.channels.keys()];
38
- }
39
-
40
- public emit<TPayload = unknown>(eventName: string, payload?: TPayload, targetSenderIds?: number[]): number {
41
- const normalizedEvent = eventName.trim();
42
-
43
- if(normalizedEvent.length === 0) {
44
- throw new Error('Renderer event name must be a non-empty string.');
45
- }
46
-
47
- const recipients = targetSenderIds ?? this.getSenderIds();
48
- let delivered = 0;
49
-
50
- for(const senderId of recipients) {
51
- const channel = this.channels.get(senderId);
52
-
53
- if(!channel) {
54
- Logger.warn(`No message channel found for sender ID: ${senderId} while emitting "${normalizedEvent}".`);
55
- continue;
56
- }
57
-
58
- try {
59
- channel.socket.port1.postMessage(createRendererEventMessage(normalizedEvent, payload));
60
- delivered++;
61
- }
62
- catch(error) {
63
- Logger.error(`[Noxus] Failed to emit "${normalizedEvent}" to sender ${senderId}.`, error);
64
- }
65
- }
66
-
67
- return delivered;
68
- }
69
-
70
- public emitToRenderer<TPayload = unknown>(senderId: number, eventName: string, payload?: TPayload): boolean {
71
- return this.emit(eventName, payload, [senderId]) > 0;
72
- }
73
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * @copyright 2025 NoxFly
3
- * @license MIT
4
- * @author NoxFly
5
- */
6
-
7
- import { Type } from "./types";
8
-
9
- /**
10
- * A function that returns a type.
11
- * Used for forward references to types that are not yet defined.
12
- */
13
- export interface ForwardRefFn<T = any> {
14
- (): Type<T>;
15
- }
16
-
17
- /**
18
- * A wrapper class for forward referenced types.
19
- */
20
- export class ForwardReference<T = any> {
21
- constructor(public readonly forwardRefFn: ForwardRefFn<T>) {}
22
- }
23
-
24
- /**
25
- * Creates a forward reference to a type.
26
- * @param fn A function that returns the type.
27
- * @returns A ForwardReference instance.
28
- */
29
- export function forwardRef<T = any>(fn: ForwardRefFn<T>): ForwardReference<T> {
30
- return new ForwardReference(fn);
31
- }