@noxfly/noxus 1.1.1 → 1.1.4
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/dist/noxus.d.mts +344 -30
- package/dist/noxus.d.ts +344 -30
- package/dist/noxus.js +183 -56
- package/dist/noxus.mjs +183 -56
- package/package.json +1 -1
- package/src/DI/app-injector.ts +38 -10
- package/src/DI/injector-explorer.ts +9 -7
- package/src/app.ts +40 -7
- package/src/bootstrap.ts +6 -1
- package/src/decorators/controller.decorator.ts +18 -3
- package/src/decorators/guards.decorator.ts +25 -5
- package/src/decorators/injectable.decorator.ts +16 -3
- package/src/decorators/method.decorator.ts +65 -11
- package/src/decorators/middleware.decorator.ts +34 -4
- package/src/decorators/module.decorator.ts +17 -11
- package/src/request.ts +14 -0
- package/src/router.ts +76 -19
- package/src/utils/logger.ts +109 -31
- package/src/utils/radix-tree.ts +71 -4
- package/src/utils/types.ts +8 -3
- package/tsup.config.ts +2 -1
- package/dist/noxus.mjs.map +0 -1
package/src/router.ts
CHANGED
|
@@ -16,8 +16,11 @@ import { Logger } from 'src/utils/logger';
|
|
|
16
16
|
import { RadixTree } from 'src/utils/radix-tree';
|
|
17
17
|
import { Type } from 'src/utils/types';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* IRouteDefinition interface defines the structure of a route in the application.
|
|
21
|
+
* It includes the HTTP method, path, controller class, handler method name,
|
|
22
|
+
* guards, and middlewares associated with the route.
|
|
23
|
+
*/
|
|
21
24
|
export interface IRouteDefinition {
|
|
22
25
|
method: string;
|
|
23
26
|
path: string;
|
|
@@ -27,23 +30,34 @@ export interface IRouteDefinition {
|
|
|
27
30
|
middlewares: Type<IMiddleware>[];
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* This type defines a function that represents an action in a controller.
|
|
35
|
+
* It takes a Request and an IResponse as parameters and returns a value or a Promise.
|
|
36
|
+
*/
|
|
30
37
|
export type ControllerAction = (request: Request, response: IResponse) => any;
|
|
31
38
|
|
|
32
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Router class is responsible for managing the application's routing.
|
|
42
|
+
* It registers controllers, handles requests, and manages middlewares and guards.
|
|
43
|
+
*/
|
|
33
44
|
@Injectable('singleton')
|
|
34
45
|
export class Router {
|
|
35
46
|
private readonly routes = new RadixTree<IRouteDefinition>();
|
|
36
47
|
private readonly rootMiddlewares: Type<IMiddleware>[] = [];
|
|
37
48
|
|
|
38
49
|
/**
|
|
39
|
-
*
|
|
50
|
+
* Registers a controller class with the router.
|
|
51
|
+
* This method extracts the route metadata from the controller class and registers it in the routing tree.
|
|
52
|
+
* It also handles the guards and middlewares associated with the controller.
|
|
53
|
+
* @param controllerClass - The controller class to register.
|
|
40
54
|
*/
|
|
41
55
|
public registerController(controllerClass: Type<unknown>): Router {
|
|
42
56
|
const controllerMeta = getControllerMetadata(controllerClass);
|
|
43
57
|
|
|
44
58
|
const controllerGuards = getGuardForController(controllerClass.name);
|
|
45
59
|
const controllerMiddlewares = getMiddlewaresForController(controllerClass.name);
|
|
46
|
-
|
|
60
|
+
|
|
47
61
|
if(!controllerMeta)
|
|
48
62
|
throw new Error(`Missing @Controller decorator on ${controllerClass.name}`);
|
|
49
63
|
|
|
@@ -66,7 +80,7 @@ export class Router {
|
|
|
66
80
|
guards: [...guards],
|
|
67
81
|
middlewares: [...middlewares],
|
|
68
82
|
};
|
|
69
|
-
|
|
83
|
+
|
|
70
84
|
this.routes.insert(fullPath + '/' + def.method, routeDef);
|
|
71
85
|
|
|
72
86
|
const hasActionGuards = routeDef.guards.length > 0;
|
|
@@ -79,7 +93,7 @@ export class Router {
|
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
const hasCtrlGuards = controllerMeta.guards.length > 0;
|
|
82
|
-
|
|
96
|
+
|
|
83
97
|
const controllerGuardsInfo = hasCtrlGuards
|
|
84
98
|
? '<' + controllerMeta.guards.map(g => g.name).join('|') + '>'
|
|
85
99
|
: '';
|
|
@@ -90,7 +104,10 @@ export class Router {
|
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
/**
|
|
93
|
-
*
|
|
107
|
+
* Defines a middleware for the root of the application.
|
|
108
|
+
* This method allows you to register a middleware that will be applied to all requests
|
|
109
|
+
* to the application, regardless of the controller or action.
|
|
110
|
+
* @param middleware - The middleware class to register.
|
|
94
111
|
*/
|
|
95
112
|
public defineRootMiddleware(middleware: Type<IMiddleware>): Router {
|
|
96
113
|
Logger.debug(`Registering root middleware: ${middleware.name}`);
|
|
@@ -99,13 +116,16 @@ export class Router {
|
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
|
-
*
|
|
119
|
+
* Shuts down the message channel for a specific sender ID.
|
|
120
|
+
* This method closes the IPC channel for the specified sender ID and
|
|
121
|
+
* removes it from the messagePorts map.
|
|
122
|
+
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
103
123
|
*/
|
|
104
124
|
public async handle(request: Request): Promise<IResponse> {
|
|
105
125
|
Logger.log(`> Received request: {${request.method} /${request.path}}`);
|
|
106
126
|
|
|
107
127
|
const t0 = performance.now();
|
|
108
|
-
|
|
128
|
+
|
|
109
129
|
const response: IResponse = {
|
|
110
130
|
requestId: request.id,
|
|
111
131
|
status: 200,
|
|
@@ -156,7 +176,11 @@ export class Router {
|
|
|
156
176
|
}
|
|
157
177
|
|
|
158
178
|
/**
|
|
159
|
-
*
|
|
179
|
+
* Finds the route definition for a given request.
|
|
180
|
+
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
181
|
+
* If no matching route is found, it throws a NotFoundException.
|
|
182
|
+
* @param request - The Request object containing the method and path to search for.
|
|
183
|
+
* @returns The IRouteDefinition for the matched route.
|
|
160
184
|
*/
|
|
161
185
|
private findRoute(request: Request): IRouteDefinition {
|
|
162
186
|
const matchedRoutes = this.routes.search(request.path);
|
|
@@ -175,7 +199,14 @@ export class Router {
|
|
|
175
199
|
}
|
|
176
200
|
|
|
177
201
|
/**
|
|
178
|
-
*
|
|
202
|
+
* Resolves the controller for a given route definition.
|
|
203
|
+
* This method creates an instance of the controller class and prepares the request parameters.
|
|
204
|
+
* It also runs the request pipeline, which includes executing middlewares and guards.
|
|
205
|
+
* @param request - The Request object containing the request data.
|
|
206
|
+
* @param response - The IResponse object to populate with the response data.
|
|
207
|
+
* @param routeDef - The IRouteDefinition for the matched route.
|
|
208
|
+
* @return A Promise that resolves when the controller action has been executed.
|
|
209
|
+
* @throws UnauthorizedException if the request is not authorized by the guards.
|
|
179
210
|
*/
|
|
180
211
|
private async resolveController(request: Request, response: IResponse, routeDef: IRouteDefinition): Promise<void> {
|
|
181
212
|
const controllerInstance = request.context.resolve(routeDef.controller);
|
|
@@ -186,7 +217,15 @@ export class Router {
|
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
/**
|
|
189
|
-
*
|
|
220
|
+
* Runs the request pipeline for a given request.
|
|
221
|
+
* This method executes the middlewares and guards associated with the route,
|
|
222
|
+
* and finally calls the controller action.
|
|
223
|
+
* @param request - The Request object containing the request data.
|
|
224
|
+
* @param response - The IResponse object to populate with the response data.
|
|
225
|
+
* @param routeDef - The IRouteDefinition for the matched route.
|
|
226
|
+
* @param controllerInstance - The instance of the controller class.
|
|
227
|
+
* @return A Promise that resolves when the request pipeline has been executed.
|
|
228
|
+
* @throws ResponseException if the response status is not successful.
|
|
190
229
|
*/
|
|
191
230
|
private async runRequestPipeline(request: Request, response: IResponse, routeDef: IRouteDefinition, controllerInstance: any): Promise<void> {
|
|
192
231
|
const middlewares = [...new Set([...this.rootMiddlewares, ...routeDef.middlewares])];
|
|
@@ -199,7 +238,7 @@ export class Router {
|
|
|
199
238
|
const dispatch = async (i: number): Promise<void> => {
|
|
200
239
|
if(i <= index)
|
|
201
240
|
throw new Error("next() called multiple times");
|
|
202
|
-
|
|
241
|
+
|
|
203
242
|
index = i;
|
|
204
243
|
|
|
205
244
|
// middlewares
|
|
@@ -213,7 +252,7 @@ export class Router {
|
|
|
213
252
|
|
|
214
253
|
return;
|
|
215
254
|
}
|
|
216
|
-
|
|
255
|
+
|
|
217
256
|
// guards
|
|
218
257
|
if(i <= guardsMaxIndex) {
|
|
219
258
|
const guardIndex = i - middlewares.length;
|
|
@@ -232,7 +271,14 @@ export class Router {
|
|
|
232
271
|
}
|
|
233
272
|
|
|
234
273
|
/**
|
|
235
|
-
*
|
|
274
|
+
* Runs a middleware function in the request pipeline.
|
|
275
|
+
* This method creates an instance of the middleware and invokes its `invoke` method,
|
|
276
|
+
* passing the request, response, and next function.
|
|
277
|
+
* @param request - The Request object containing the request data.
|
|
278
|
+
* @param response - The IResponse object to populate with the response data.
|
|
279
|
+
* @param next - The NextFunction to call to continue the middleware chain.
|
|
280
|
+
* @param middlewareType - The type of the middleware to run.
|
|
281
|
+
* @return A Promise that resolves when the middleware has been executed.
|
|
236
282
|
*/
|
|
237
283
|
private async runMiddleware(request: Request, response: IResponse, next: NextFunction, middlewareType: Type<IMiddleware>): Promise<void> {
|
|
238
284
|
const middleware = request.context.resolve(middlewareType);
|
|
@@ -240,7 +286,13 @@ export class Router {
|
|
|
240
286
|
}
|
|
241
287
|
|
|
242
288
|
/**
|
|
243
|
-
*
|
|
289
|
+
* Runs a guard to check if the request is authorized.
|
|
290
|
+
* This method creates an instance of the guard and calls its `canActivate` method.
|
|
291
|
+
* If the guard returns false, it throws an UnauthorizedException.
|
|
292
|
+
* @param request - The Request object containing the request data.
|
|
293
|
+
* @param guardType - The type of the guard to run.
|
|
294
|
+
* @return A Promise that resolves if the guard allows the request, or throws an UnauthorizedException if not.
|
|
295
|
+
* @throws UnauthorizedException if the guard denies access to the request.
|
|
244
296
|
*/
|
|
245
297
|
private async runGuard(request: Request, guardType: Type<IGuard>): Promise<void> {
|
|
246
298
|
const guard = request.context.resolve(guardType);
|
|
@@ -251,19 +303,24 @@ export class Router {
|
|
|
251
303
|
}
|
|
252
304
|
|
|
253
305
|
/**
|
|
254
|
-
*
|
|
306
|
+
* Extracts parameters from the actual request path based on the template path.
|
|
307
|
+
* This method splits the actual path and the template path into segments,
|
|
308
|
+
* then maps the segments to parameters based on the template.
|
|
309
|
+
* @param actual - The actual request path.
|
|
310
|
+
* @param template - The template path to extract parameters from.
|
|
311
|
+
* @returns An object containing the extracted parameters.
|
|
255
312
|
*/
|
|
256
313
|
private extractParams(actual: string, template: string): Record<string, string> {
|
|
257
314
|
const aParts = actual.split('/');
|
|
258
315
|
const tParts = template.split('/');
|
|
259
316
|
const params: Record<string, string> = {};
|
|
260
|
-
|
|
317
|
+
|
|
261
318
|
tParts.forEach((part, i) => {
|
|
262
319
|
if(part.startsWith(':')) {
|
|
263
320
|
params[part.slice(1)] = aParts[i] ?? '';
|
|
264
321
|
}
|
|
265
322
|
});
|
|
266
|
-
|
|
323
|
+
|
|
267
324
|
return params;
|
|
268
325
|
}
|
|
269
326
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -4,12 +4,27 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Logger is a utility class for logging messages to the console.
|
|
9
|
+
*/
|
|
10
|
+
export type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns a formatted timestamp for logging.
|
|
14
|
+
*/
|
|
7
15
|
function getPrettyTimestamp(): string {
|
|
8
16
|
const now = new Date();
|
|
9
17
|
return `${now.getDate().toString().padStart(2, '0')}/${(now.getMonth() + 1).toString().padStart(2, '0')}/${now.getFullYear()}`
|
|
10
18
|
+ ` ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Generates a log prefix for the console output.
|
|
23
|
+
* @param callee - The name of the function or class that is logging the message.
|
|
24
|
+
* @param messageType - The type of message being logged (e.g., 'log', 'info', 'warn', 'error', 'debug').
|
|
25
|
+
* @param color - The color to use for the log message.
|
|
26
|
+
* @returns A formatted string that includes the timestamp, process ID, message type, and callee name.
|
|
27
|
+
*/
|
|
13
28
|
function getLogPrefix(callee: string, messageType: string, color: string): string {
|
|
14
29
|
const timestamp = getPrettyTimestamp();
|
|
15
30
|
|
|
@@ -21,9 +36,16 @@ function getLogPrefix(callee: string, messageType: string, color: string): strin
|
|
|
21
36
|
+ `${Logger.colors.yellow}[${callee}]${Logger.colors.initial}`;
|
|
22
37
|
}
|
|
23
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Formats an object into a string representation for logging.
|
|
41
|
+
* It converts the object to JSON and adds indentation for readability.
|
|
42
|
+
* @param prefix - The prefix to use for the formatted object.
|
|
43
|
+
* @param arg - The object to format.
|
|
44
|
+
* @returns A formatted string representation of the object, with each line prefixed by the specified prefix.
|
|
45
|
+
*/
|
|
24
46
|
function formatObject(prefix: string, arg: object): string {
|
|
25
47
|
const json = JSON.stringify(arg, null, 2);
|
|
26
|
-
|
|
48
|
+
|
|
27
49
|
const prefixedJson = json
|
|
28
50
|
.split('\n')
|
|
29
51
|
.map((line, idx) => idx === 0 ? `${Logger.colors.darkGrey}${line}` : `${prefix} ${Logger.colors.grey}${line}`)
|
|
@@ -32,6 +54,15 @@ function formatObject(prefix: string, arg: object): string {
|
|
|
32
54
|
return prefixedJson;
|
|
33
55
|
}
|
|
34
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Formats the arguments for logging.
|
|
59
|
+
* It colors strings and formats objects with indentation.
|
|
60
|
+
* This function is used to prepare the arguments for console output.
|
|
61
|
+
* @param prefix - The prefix to use for the formatted arguments.
|
|
62
|
+
* @param args - The arguments to format.
|
|
63
|
+
* @param color - The color to use for the formatted arguments.
|
|
64
|
+
* @returns An array of formatted arguments, where strings are colored and objects are formatted with indentation.
|
|
65
|
+
*/
|
|
35
66
|
function formattedArgs(prefix: string, args: any[], color: string): any[] {
|
|
36
67
|
return args.map(arg => {
|
|
37
68
|
if(typeof arg === 'string') {
|
|
@@ -46,13 +77,29 @@ function formattedArgs(prefix: string, args: any[], color: string): any[] {
|
|
|
46
77
|
});
|
|
47
78
|
}
|
|
48
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Gets the name of the caller function or class from the stack trace.
|
|
82
|
+
* This function is used to determine the context of the log message.
|
|
83
|
+
* @returns The name of the caller function or class.
|
|
84
|
+
*/
|
|
49
85
|
function getCallee(): string {
|
|
50
86
|
const stack = new Error().stack?.split('\n') ?? [];
|
|
51
87
|
const caller = stack[3]?.trim().match(/at (.+?)(?:\..+)? .+$/)?.[1]?.replace('Object', '') || "App";
|
|
52
88
|
return caller;
|
|
53
89
|
}
|
|
54
90
|
|
|
55
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Checks if the current log level allows logging the specified level.
|
|
93
|
+
* This function compares the current log level with the specified level to determine if logging should occur.
|
|
94
|
+
* @param level - The log level to check.
|
|
95
|
+
* @returns A boolean indicating whether the log level is enabled.
|
|
96
|
+
*/
|
|
97
|
+
function canLog(level: LogLevel): boolean {
|
|
98
|
+
return logLevelRank[level] >= logLevelRank[logLevel];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
let logLevel: LogLevel = 'log';
|
|
56
103
|
|
|
57
104
|
const logLevelRank: Record<LogLevel, number> = {
|
|
58
105
|
debug: 0,
|
|
@@ -62,39 +109,24 @@ const logLevelRank: Record<LogLevel, number> = {
|
|
|
62
109
|
error: 4,
|
|
63
110
|
};
|
|
64
111
|
|
|
65
|
-
function canLog(level: LogLevel): boolean {
|
|
66
|
-
return logLevelRank[level] >= logLevelRank[logLevel];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let logLevel: LogLevel = 'log';
|
|
70
|
-
|
|
71
112
|
export namespace Logger {
|
|
72
|
-
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Sets the log level for the logger.
|
|
116
|
+
* This function allows you to change the log level dynamically at runtime.
|
|
117
|
+
* This won't affect the startup logs.
|
|
118
|
+
* @param level Sets the log level for the logger.
|
|
119
|
+
*/
|
|
73
120
|
export function setLogLevel(level: LogLevel): void {
|
|
74
121
|
logLevel = level;
|
|
75
122
|
}
|
|
76
123
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
blue: '\x1b[0;34m',
|
|
84
|
-
purple: '\x1b[0;35m',
|
|
85
|
-
|
|
86
|
-
darkGrey: '\x1b[1;30m',
|
|
87
|
-
lightRed: '\x1b[1;31m',
|
|
88
|
-
lightGreen: '\x1b[1;32m',
|
|
89
|
-
yellow: '\x1b[1;33m',
|
|
90
|
-
lightBlue: '\x1b[1;34m',
|
|
91
|
-
magenta: '\x1b[1;35m',
|
|
92
|
-
cyan: '\x1b[1;36m',
|
|
93
|
-
white: '\x1b[1;37m',
|
|
94
|
-
|
|
95
|
-
initial: '\x1b[0m'
|
|
96
|
-
};
|
|
97
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Logs a message to the console with log level LOG.
|
|
126
|
+
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
127
|
+
* It uses different colors for different log levels to enhance readability.
|
|
128
|
+
* @param args The arguments to log.
|
|
129
|
+
*/
|
|
98
130
|
export function log(...args: any[]): void {
|
|
99
131
|
if(!canLog('log'))
|
|
100
132
|
return;
|
|
@@ -104,6 +136,12 @@ export namespace Logger {
|
|
|
104
136
|
console.log(prefix, ...formattedArgs(prefix, args, colors.green));
|
|
105
137
|
}
|
|
106
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Logs a message to the console with log level INFO.
|
|
141
|
+
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
142
|
+
* It uses different colors for different log levels to enhance readability.
|
|
143
|
+
* @param args The arguments to log.
|
|
144
|
+
*/
|
|
107
145
|
export function info(...args: any[]): void {
|
|
108
146
|
if(!canLog('info'))
|
|
109
147
|
return;
|
|
@@ -113,6 +151,12 @@ export namespace Logger {
|
|
|
113
151
|
console.info(prefix, ...formattedArgs(prefix, args, colors.blue));
|
|
114
152
|
}
|
|
115
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Logs a message to the console with log level WARN.
|
|
156
|
+
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
157
|
+
* It uses different colors for different log levels to enhance readability.
|
|
158
|
+
* @param args The arguments to log.
|
|
159
|
+
*/
|
|
116
160
|
export function warn(...args: any[]): void {
|
|
117
161
|
if(!canLog('warn'))
|
|
118
162
|
return;
|
|
@@ -122,6 +166,12 @@ export namespace Logger {
|
|
|
122
166
|
console.warn(prefix, ...formattedArgs(prefix, args, colors.brown));
|
|
123
167
|
}
|
|
124
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Logs a message to the console with log level ERROR.
|
|
171
|
+
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
172
|
+
* It uses different colors for different log levels to enhance readability.
|
|
173
|
+
* @param args The arguments to log.
|
|
174
|
+
*/
|
|
125
175
|
export function error(...args: any[]): void {
|
|
126
176
|
if(!canLog('error'))
|
|
127
177
|
return;
|
|
@@ -131,6 +181,12 @@ export namespace Logger {
|
|
|
131
181
|
console.error(prefix, ...formattedArgs(prefix, args, colors.red));
|
|
132
182
|
}
|
|
133
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Logs a message to the console with log level DEBUG.
|
|
186
|
+
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
187
|
+
* It uses different colors for different log levels to enhance readability.
|
|
188
|
+
* @param args The arguments to log.
|
|
189
|
+
*/
|
|
134
190
|
export function debug(...args: any[]): void {
|
|
135
191
|
if(!canLog('debug'))
|
|
136
192
|
return;
|
|
@@ -139,4 +195,26 @@ export namespace Logger {
|
|
|
139
195
|
const prefix = getLogPrefix(callee, "debug", colors.purple);
|
|
140
196
|
console.debug(prefix, ...formattedArgs(prefix, args, colors.purple));
|
|
141
197
|
}
|
|
142
|
-
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
export const colors = {
|
|
201
|
+
black: '\x1b[0;30m',
|
|
202
|
+
grey: '\x1b[0;37m',
|
|
203
|
+
red: '\x1b[0;31m',
|
|
204
|
+
green: '\x1b[0;32m',
|
|
205
|
+
brown: '\x1b[0;33m',
|
|
206
|
+
blue: '\x1b[0;34m',
|
|
207
|
+
purple: '\x1b[0;35m',
|
|
208
|
+
|
|
209
|
+
darkGrey: '\x1b[1;30m',
|
|
210
|
+
lightRed: '\x1b[1;31m',
|
|
211
|
+
lightGreen: '\x1b[1;32m',
|
|
212
|
+
yellow: '\x1b[1;33m',
|
|
213
|
+
lightBlue: '\x1b[1;34m',
|
|
214
|
+
magenta: '\x1b[1;35m',
|
|
215
|
+
cyan: '\x1b[1;36m',
|
|
216
|
+
white: '\x1b[1;37m',
|
|
217
|
+
|
|
218
|
+
initial: '\x1b[0m'
|
|
219
|
+
};
|
|
220
|
+
}
|
package/src/utils/radix-tree.ts
CHANGED
|
@@ -4,13 +4,23 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
7
10
|
type Params = Record<string, string>;
|
|
8
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Represents a search result in the Radix Tree.
|
|
14
|
+
*/
|
|
9
15
|
interface ISearchResult<T> {
|
|
10
16
|
node: RadixNode<T>;
|
|
11
17
|
params: Params;
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Represents a node in the Radix Tree.
|
|
22
|
+
* The represents a path segment
|
|
23
|
+
*/
|
|
14
24
|
class RadixNode<T> {
|
|
15
25
|
public segment: string;
|
|
16
26
|
public children: RadixNode<T>[] = [];
|
|
@@ -18,15 +28,25 @@ class RadixNode<T> {
|
|
|
18
28
|
public isParam: boolean;
|
|
19
29
|
public paramName?: string;
|
|
20
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new RadixNode.
|
|
33
|
+
* @param segment - The segment of the path this node represents.
|
|
34
|
+
*/
|
|
21
35
|
constructor(segment: string) {
|
|
22
36
|
this.segment = segment;
|
|
23
37
|
this.isParam = segment.startsWith(":");
|
|
24
|
-
|
|
38
|
+
|
|
25
39
|
if(this.isParam) {
|
|
26
40
|
this.paramName = segment.slice(1);
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Matches a child node against a given segment.
|
|
46
|
+
* This method checks if the segment matches any of the children nodes.
|
|
47
|
+
* @param segment - The segment to match against the children of this node.
|
|
48
|
+
* @returns A child node that matches the segment, or undefined if no match is found.
|
|
49
|
+
*/
|
|
30
50
|
public matchChild(segment: string): RadixNode<T> | undefined {
|
|
31
51
|
for(const child of this.children) {
|
|
32
52
|
if(child.isParam || segment.startsWith(child.segment))
|
|
@@ -36,23 +56,50 @@ class RadixNode<T> {
|
|
|
36
56
|
return undefined;
|
|
37
57
|
}
|
|
38
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Finds a child node that matches the segment exactly.
|
|
61
|
+
* This method checks if there is a child node that matches the segment exactly.
|
|
62
|
+
* @param segment - The segment to find an exact match for among the children of this node.
|
|
63
|
+
* @returns A child node that matches the segment exactly, or undefined if no match is found.
|
|
64
|
+
*/
|
|
39
65
|
public findExactChild(segment: string): RadixNode<T> | undefined {
|
|
40
66
|
return this.children.find(c => c.segment === segment);
|
|
41
67
|
}
|
|
42
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Adds a child node to this node's children.
|
|
71
|
+
* This method adds a new child node to the list of children for this node.
|
|
72
|
+
* @param node - The child node to add to this node's children.
|
|
73
|
+
*/
|
|
43
74
|
public addChild(node: RadixNode<T>): void {
|
|
44
75
|
this.children.push(node);
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
78
|
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
*/
|
|
48
82
|
export class RadixTree<T> {
|
|
49
83
|
private readonly root = new RadixNode<T>("");
|
|
50
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Inserts a path and its associated value into the Radix Tree.
|
|
87
|
+
* This method normalizes the path and inserts it into the tree, associating it with
|
|
88
|
+
* @param path - The path to insert into the tree.
|
|
89
|
+
* @param value - The value to associate with the path.
|
|
90
|
+
*/
|
|
51
91
|
public insert(path: string, value: T): void {
|
|
52
92
|
const segments = this.normalize(path);
|
|
53
93
|
this.insertRecursive(this.root, segments, value);
|
|
54
94
|
}
|
|
55
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Recursively inserts a path into the Radix Tree.
|
|
98
|
+
* This method traverses the tree and inserts the segments of the path, creating new nodes
|
|
99
|
+
* @param node - The node to start inserting from.
|
|
100
|
+
* @param segments - The segments of the path to insert.
|
|
101
|
+
* @param value - The value to associate with the path.
|
|
102
|
+
*/
|
|
56
103
|
private insertRecursive(node: RadixNode<T>, segments: string[], value: T): void {
|
|
57
104
|
if(segments.length === 0) {
|
|
58
105
|
node.value = value;
|
|
@@ -74,11 +121,25 @@ export class RadixTree<T> {
|
|
|
74
121
|
this.insertRecursive(child, segments.slice(1), value);
|
|
75
122
|
}
|
|
76
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Searches for a path in the Radix Tree.
|
|
126
|
+
* This method normalizes the path and searches for it in the tree, returning the node
|
|
127
|
+
* @param path - The path to search for in the Radix Tree.
|
|
128
|
+
* @returns An ISearchResult containing the node and parameters if a match is found, otherwise undefined.
|
|
129
|
+
*/
|
|
77
130
|
public search(path: string): ISearchResult<T> | undefined {
|
|
78
131
|
const segments = this.normalize(path);
|
|
79
132
|
return this.searchRecursive(this.root, segments, {});
|
|
80
133
|
}
|
|
81
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Recursively searches for a path in the Radix Tree.
|
|
137
|
+
* This method traverses the tree and searches for the segments of the path, collecting parameters
|
|
138
|
+
* @param node - The node to start searching from.
|
|
139
|
+
* @param segments - The segments of the path to search for.
|
|
140
|
+
* @param params - The parameters collected during the search.
|
|
141
|
+
* @returns An ISearchResult containing the node and parameters if a match is found, otherwise undefined.
|
|
142
|
+
*/
|
|
82
143
|
private searchRecursive(node: RadixNode<T>, segments: string[], params: Params): ISearchResult<T> | undefined {
|
|
83
144
|
if(segments.length === 0) {
|
|
84
145
|
if(node.value !== undefined) {
|
|
@@ -96,7 +157,7 @@ export class RadixTree<T> {
|
|
|
96
157
|
for(const child of node.children) {
|
|
97
158
|
if(child.isParam) {
|
|
98
159
|
const paramName = child.paramName!;
|
|
99
|
-
|
|
160
|
+
|
|
100
161
|
const childParams: Params = {
|
|
101
162
|
...params,
|
|
102
163
|
[paramName]: segment ?? "",
|
|
@@ -110,7 +171,7 @@ export class RadixTree<T> {
|
|
|
110
171
|
}
|
|
111
172
|
|
|
112
173
|
const result = this.searchRecursive(child, rest, childParams);
|
|
113
|
-
|
|
174
|
+
|
|
114
175
|
if(result)
|
|
115
176
|
return result;
|
|
116
177
|
}
|
|
@@ -132,6 +193,12 @@ export class RadixTree<T> {
|
|
|
132
193
|
return undefined;
|
|
133
194
|
}
|
|
134
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Normalizes a path into an array of segments.
|
|
198
|
+
* This method removes leading and trailing slashes, splits the path by slashes, and
|
|
199
|
+
* @param path - The path to normalize.
|
|
200
|
+
* @returns An array of normalized path segments.
|
|
201
|
+
*/
|
|
135
202
|
private normalize(path: string): string[] {
|
|
136
203
|
const segments = path
|
|
137
204
|
.replace(/^\/+|\/+$/g, "")
|
|
@@ -139,5 +206,5 @@ export class RadixTree<T> {
|
|
|
139
206
|
.filter(Boolean);
|
|
140
207
|
|
|
141
208
|
return ['', ...segments];
|
|
142
|
-
}
|
|
209
|
+
}
|
|
143
210
|
}
|
package/src/utils/types.ts
CHANGED
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
8
|
-
|
|
9
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Represents a generic type that can be either a class or a function.
|
|
10
|
+
* This is used to define types that can be instantiated or called.
|
|
11
|
+
*/
|
|
10
12
|
declare const Type: FunctionConstructor;
|
|
11
13
|
export interface Type<T> extends Function {
|
|
12
14
|
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
13
15
|
new (...args: any[]): T;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Represents a generic type that can be either a value or a promise resolving to that value.
|
|
20
|
+
*/
|
|
21
|
+
export type MaybeAsync<T> = T | Promise<T>;
|
package/tsup.config.ts
CHANGED
|
@@ -13,6 +13,7 @@ export default defineConfig({
|
|
|
13
13
|
noxus: "src/index.ts"
|
|
14
14
|
},
|
|
15
15
|
keepNames: true,
|
|
16
|
+
minifyIdentifiers: false,
|
|
16
17
|
name: "noxus",
|
|
17
18
|
format: ["cjs", "esm"],
|
|
18
19
|
dts: true,
|
|
@@ -28,4 +29,4 @@ export default defineConfig({
|
|
|
28
29
|
banner: {
|
|
29
30
|
js: copyrights,
|
|
30
31
|
}
|
|
31
|
-
});
|
|
32
|
+
});
|