@noxfly/noxus 1.0.4 → 1.0.5

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.
@@ -0,0 +1,26 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const frameworkName = 'noxus';
5
+
6
+ function removeDuplicateCopyrights(filename) {
7
+ const filepath = path.join(__dirname, '../dist/' + filename);
8
+ const content = fs.readFileSync(filepath, 'utf8');
9
+
10
+ const reg = /\/\*\*[\t ]*\n(?: \*.*\n)*? \* *@copyright.*\n(?: \*.*\n)*? \*\/\n?/gm;
11
+
12
+ let first = true;
13
+ const deduped = content.replace(reg, (match) => {
14
+ if (first) {
15
+ first = false;
16
+ return match; // keep the first
17
+ }
18
+ return ''; // remove others
19
+ });
20
+
21
+ fs.writeFileSync(filepath, deduped);
22
+ }
23
+
24
+
25
+ removeDuplicateCopyrights(`${frameworkName}.d.mts`);
26
+ removeDuplicateCopyrights(`${frameworkName}.d.ts`);
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import 'reflect-metadata';
2
8
  import { InternalServerException } from 'src/exceptions';
3
9
  import { Type } from 'src/utils/types';
@@ -78,3 +84,7 @@ class AppInjector {
78
84
  }
79
85
 
80
86
  export const RootInjector = new AppInjector('root');
87
+
88
+ export function inject<T>(t: Type<T>): T {
89
+ return RootInjector.resolve(t);
90
+ }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import { getControllerMetadata } from "src/decorators/controller.decorator";
2
8
  import { getInjectableMetadata } from "src/decorators/injectable.decorator";
3
9
  import { getRouteMetadata } from "src/decorators/method.decorator";
package/src/app.ts CHANGED
@@ -1,4 +1,151 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
7
+ import { app, BrowserWindow, ipcMain, MessageChannelMain } from "electron/main";
8
+ import { Injectable } from "src/decorators/injectable.decorator";
9
+ import { inject } from "src/DI/app-injector";
10
+ import { IRequest, IResponse, Request } from "src/request";
11
+ import { Router } from "src/router";
12
+ import { Logger } from "src/utils/logger";
13
+ import { Type } from "src/utils/types";
14
+
1
15
  export interface IApp {
2
16
  dispose(): Promise<void>;
3
17
  onReady(): Promise<void>;
18
+ onActivated(): Promise<void>;
4
19
  }
20
+
21
+ @Injectable('singleton')
22
+ export class NoxApp {
23
+ private readonly messagePorts = new Map<number, Electron.MessageChannelMain>();
24
+ private app: IApp | undefined;
25
+
26
+ constructor(
27
+ private readonly router: Router,
28
+ ) {}
29
+
30
+ /**
31
+ *
32
+ */
33
+ public async init(): Promise<NoxApp> {
34
+ ipcMain.on('gimme-my-port', this.giveTheRendererAPort.bind(this));
35
+
36
+ app.once('activate', this.onAppActivated.bind(this));
37
+ app.once('window-all-closed', this.onAllWindowsClosed.bind(this));
38
+
39
+ console.log(''); // create a new line in the console to separate setup logs from the future logs
40
+
41
+ return this;
42
+ }
43
+
44
+ /**
45
+ *
46
+ */
47
+ private giveTheRendererAPort(event: Electron.IpcMainInvokeEvent): void {
48
+ const senderId = event.sender.id;
49
+
50
+ if(this.messagePorts.has(senderId)) {
51
+ this.shutdownChannel(senderId);
52
+ }
53
+
54
+ const channel = new MessageChannelMain();
55
+ this.messagePorts.set(senderId, channel);
56
+
57
+ channel.port1.on('message', this.onRendererMessage.bind(this));
58
+ channel.port1.start();
59
+
60
+ event.sender.postMessage('port', { senderId }, [channel.port2]);
61
+ }
62
+
63
+ /**
64
+ * Electron specific message handling.
65
+ * Replaces HTTP calls by using Electron's IPC mechanism.
66
+ */
67
+ private async onRendererMessage(event: Electron.MessageEvent): Promise<void> {
68
+ const { senderId, requestId, path, method, body }: IRequest = event.data;
69
+
70
+ const channel = this.messagePorts.get(senderId);
71
+
72
+ if(!channel) {
73
+ Logger.error(`No message channel found for sender ID: ${senderId}`);
74
+ return;
75
+ }
76
+
77
+ try {
78
+ const request = new Request(event, requestId, method, path, body);
79
+ const response = await this.router.handle(request);
80
+ channel.port1.postMessage(response);
81
+ }
82
+ catch(err: any) {
83
+ const response: IResponse = {
84
+ requestId,
85
+ status: 500,
86
+ body: null,
87
+ error: err.message || 'Internal Server Error',
88
+ };
89
+
90
+ channel.port1.postMessage(response);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * MacOS specific behavior.
96
+ */
97
+ private onAppActivated(): void {
98
+ if(process.platform === 'darwin' && BrowserWindow.getAllWindows().length === 0) {
99
+ this.app?.onActivated();
100
+ }
101
+ }
102
+
103
+ private shutdownChannel(channelSenderId: number, remove: boolean = true): void {
104
+ const channel = this.messagePorts.get(channelSenderId);
105
+
106
+ if(!channel) {
107
+ Logger.warn(`No message channel found for sender ID: ${channelSenderId}`);
108
+ return;
109
+ }
110
+
111
+ channel.port1.off('message', this.onRendererMessage.bind(this));
112
+ channel.port1.close();
113
+ channel.port2.close();
114
+
115
+ this.messagePorts.delete(channelSenderId);
116
+ }
117
+
118
+ /**
119
+ *
120
+ */
121
+ private async onAllWindowsClosed(): Promise<void> {
122
+ this.messagePorts.forEach((channel, senderId) => {
123
+ this.shutdownChannel(senderId, false);
124
+ });
125
+
126
+ this.messagePorts.clear();
127
+
128
+ this.app?.dispose();
129
+
130
+ if(process.platform !== 'darwin') {
131
+ app.quit();
132
+ }
133
+ }
134
+
135
+
136
+ // ---
137
+
138
+
139
+ public configure(app: Type<IApp>): NoxApp {
140
+ this.app = inject(app);
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Should be called after the bootstrapApplication function is called.
146
+ */
147
+ public start(): NoxApp {
148
+ this.app?.onReady();
149
+ return this;
150
+ }
151
+ }
package/src/bootstrap.ts CHANGED
@@ -1,128 +1,29 @@
1
- import { ipcMain } from "electron";
2
- import { app, BrowserWindow, MessageChannelMain } from "electron/main";
3
- import { IApp } from "src/app";
4
- import { getInjectableMetadata } from "src/decorators/injectable.decorator";
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
7
+ import { app } from "electron/main";
8
+ import { NoxApp } from "src/app";
5
9
  import { getModuleMetadata } from "src/decorators/module.decorator";
6
- import { RootInjector } from "src/DI/app-injector";
7
- import { IRequest, IResponse, Request } from "src/request";
8
- import { Router } from "src/router";
10
+ import { inject } from "src/DI/app-injector";
9
11
  import { Type } from "src/utils/types";
10
12
 
11
13
  /**
12
14
  *
13
15
  */
14
- export async function bootstrapApplication(root: Type<IApp>, rootModule: Type<any>): Promise<IApp> {
16
+ export async function bootstrapApplication(rootModule: Type<any>): Promise<NoxApp> {
15
17
  if(!getModuleMetadata(rootModule)) {
16
18
  throw new Error(`Root module must be decorated with @Module`);
17
19
  }
18
20
 
19
- if(!getInjectableMetadata(root)) {
20
- throw new Error(`Root application must be decorated with @Injectable`);
21
- }
22
-
23
21
  await app.whenReady();
24
22
 
25
- RootInjector.resolve(Router);
26
-
27
- const noxEngine = new Nox(root, rootModule);
28
-
29
- const application = await noxEngine.init();
30
-
31
- return application;
32
- }
23
+ const noxApp = inject(NoxApp);
33
24
 
25
+ await noxApp.init();
34
26
 
35
- class Nox {
36
- private messagePort: Electron.MessageChannelMain | undefined;
37
-
38
- constructor(
39
- public readonly root: Type<IApp>,
40
- public readonly rootModule: Type<any>
41
- ) {}
42
-
43
- /**
44
- *
45
- */
46
- public async init(): Promise<IApp> {
47
- const application = RootInjector.resolve(this.root);
48
-
49
- ipcMain.on('gimme-my-port', this.giveTheClientAPort.bind(this, application));
50
-
51
- app.once('activate', this.onAppActivated.bind(this, application));
52
- app.once('window-all-closed', this.onAllWindowsClosed.bind(this, application));
53
-
54
- await application.onReady();
55
-
56
- console.log(''); // create a new line in the console to separate setup logs from the future logs
57
-
58
- return application;
59
- }
60
-
61
- /**
62
- *
63
- */
64
- private giveTheClientAPort(application: IApp, event: Electron.IpcMainInvokeEvent): void {
65
- if(this.messagePort) {
66
- this.messagePort.port1.close();
67
- this.messagePort.port2.close();
68
- this.messagePort = undefined;
69
- }
70
-
71
- this.messagePort = new MessageChannelMain();
72
-
73
- this.messagePort.port1.on('message', event => this.onClientMessage(application, event));
74
- this.messagePort.port1.start();
75
-
76
- event.sender.postMessage('port', null, [this.messagePort.port2]);
77
- }
78
-
79
- /**
80
- * Electron specific message handling.
81
- * Replaces HTTP calls by using Electron's IPC mechanism.
82
- */
83
- private async onClientMessage(application: IApp, event: Electron.MessageEvent): Promise<void> {
84
- const { requestId, path, method, body }: IRequest = event.data;
85
-
86
- try {
87
-
88
- const request = new Request(application, event, requestId, method, path, body);
89
- const router = RootInjector.resolve(Router);
90
-
91
- const response = await router.handle(request);
92
-
93
- this.messagePort?.port1.postMessage(response);
94
- }
95
- catch(err: any) {
96
- const response: IResponse = {
97
- requestId,
98
- status: 500,
99
- body: null,
100
- error: err.message || 'Internal Server Error',
101
- };
102
-
103
- this.messagePort?.port1.postMessage(response);
104
- }
105
- }
106
-
107
- /**
108
- *
109
- */
110
- private onAppActivated(application: IApp): void {
111
- if(BrowserWindow.getAllWindows().length === 0) {
112
- application.onReady();
113
- }
114
- }
115
-
116
- /**
117
- *
118
- */
119
- private async onAllWindowsClosed(application: IApp): Promise<void> {
120
- this.messagePort?.port1.close();
121
- await application.dispose();
122
-
123
- if(process.platform !== 'darwin') {
124
- app.quit();
125
- }
126
- }
27
+ return noxApp;
127
28
  }
128
29
 
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import { getGuardForController, IGuard } from "src/decorators/guards.decorator";
2
8
  import { Injectable } from "src/decorators/injectable.decorator";
3
9
  import { Type } from "src/utils/types";
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import { Request } from 'src/request';
2
8
  import { Logger } from 'src/utils/logger';
3
9
  import { MaybeAsync, Type } from 'src/utils/types';
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import { Lifetime } from "src/DI/app-injector";
2
8
  import { InjectorExplorer } from "src/DI/injector-explorer";
3
9
  import { Type } from "src/utils/types";
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import { getGuardForControllerAction, IGuard } from "src/decorators/guards.decorator";
2
8
  import { Type } from "src/utils/types";
3
9
 
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  /* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
8
 
3
9
  import { CONTROLLER_METADATA_KEY } from "src/decorators/controller.decorator";
package/src/exceptions.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  export abstract class ResponseException extends Error {
2
8
  public abstract readonly status: number;
3
9
 
@@ -12,6 +18,7 @@ export abstract class ResponseException extends Error {
12
18
  // 4XX
13
19
  export class BadRequestException extends ResponseException { public readonly status = 400; }
14
20
  export class UnauthorizedException extends ResponseException { public readonly status = 401; }
21
+ export class PaymentRequiredException extends ResponseException { public readonly status = 402; }
15
22
  export class ForbiddenException extends ResponseException { public readonly status = 403; }
16
23
  export class NotFoundException extends ResponseException { public readonly status = 404; }
17
24
  export class MethodNotAllowedException extends ResponseException { public readonly status = 405; }
package/src/index.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  export * from './DI/app-injector';
2
8
  export * from './router';
3
9
  export * from './app';
package/src/request.ts CHANGED
@@ -1,18 +1,19 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import 'reflect-metadata';
2
- import { IApp } from 'src/app';
3
8
  import { HttpMethod } from 'src/decorators/method.decorator';
4
9
  import { RootInjector } from 'src/DI/app-injector';
5
10
 
6
-
7
- //
8
-
9
11
  export class Request {
10
12
  public readonly context: any = RootInjector.createScope();
11
13
 
12
14
  public readonly params: Record<string, string> = {};
13
15
 
14
16
  constructor(
15
- public readonly app: IApp,
16
17
  public readonly event: Electron.MessageEvent,
17
18
  public readonly id: string,
18
19
  public readonly method: HttpMethod,
@@ -24,6 +25,7 @@ export class Request {
24
25
  }
25
26
 
26
27
  export interface IRequest<T = any> {
28
+ senderId: number;
27
29
  requestId: string;
28
30
  path: string;
29
31
  method: HttpMethod;
package/src/router.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  import 'reflect-metadata';
2
8
  import { getControllerMetadata } from 'src/decorators/controller.decorator';
3
9
  import { getGuardForController, getGuardForControllerAction, IGuard } from 'src/decorators/guards.decorator';
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  function getPrettyTimestamp(): string {
2
8
  const now = new Date();
3
9
  return `${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  type Params = Record<string, string>;
2
8
 
3
9
  interface ISearchResult<T> {
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @copyright 2025 NoxFly
3
+ * @license MIT
4
+ * @author NoxFly
5
+ */
6
+
1
7
  /* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
8
 
3
9
 
package/tsup.config.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { defineConfig } from "tsup";
2
2
 
3
+ const copyrights = `
4
+ /**
5
+ * @copyright 2025 NoxFly
6
+ * @license MIT
7
+ * @author NoxFly
8
+ */
9
+ `.trim()
10
+
3
11
  export default defineConfig({
4
12
  entry: {
5
13
  noxus: "src/index.ts"
@@ -17,4 +25,7 @@ export default defineConfig({
17
25
  splitting: false,
18
26
  shims: false,
19
27
  treeshake: false,
28
+ banner: {
29
+ js: copyrights,
30
+ }
20
31
  });