@noxfly/noxus 1.1.8 → 1.1.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.
- package/.vscode/settings.json +3 -0
- package/dist/noxus.d.mts +28 -6
- package/dist/noxus.d.ts +28 -6
- package/dist/noxus.js +144 -27
- package/dist/noxus.mjs +143 -26
- package/package.json +1 -1
- package/src/decorators/method.decorator.ts +6 -1
- package/src/request.ts +21 -5
- package/src/router.ts +142 -4
- package/src/utils/logger.ts +9 -0
package/dist/noxus.d.mts
CHANGED
|
@@ -86,7 +86,11 @@ interface IRouteMetadata {
|
|
|
86
86
|
/**
|
|
87
87
|
* The different HTTP methods that can be used in the application.
|
|
88
88
|
*/
|
|
89
|
-
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
89
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'BATCH';
|
|
90
|
+
/**
|
|
91
|
+
* Atomic HTTP verbs supported by controllers. BATCH is handled at the router level only.
|
|
92
|
+
*/
|
|
93
|
+
type AtomicHttpMethod = Exclude<HttpMethod, 'BATCH'>;
|
|
90
94
|
/**
|
|
91
95
|
* Gets the route metadata for a given target class.
|
|
92
96
|
* This metadata includes the HTTP method, path, handler, and guards defined by the route decorators.
|
|
@@ -152,22 +156,35 @@ declare class Request {
|
|
|
152
156
|
* It includes properties for the sender ID, request ID, path, method, and an optional body.
|
|
153
157
|
* This interface is used to standardize the request data across the application.
|
|
154
158
|
*/
|
|
155
|
-
interface IRequest<
|
|
159
|
+
interface IRequest<TBody = unknown> {
|
|
156
160
|
senderId: number;
|
|
157
161
|
requestId: string;
|
|
158
162
|
path: string;
|
|
159
163
|
method: HttpMethod;
|
|
160
|
-
body?:
|
|
164
|
+
body?: TBody;
|
|
165
|
+
}
|
|
166
|
+
interface IBatchRequestItem<TBody = unknown> {
|
|
167
|
+
requestId?: string;
|
|
168
|
+
path: string;
|
|
169
|
+
method: AtomicHttpMethod;
|
|
170
|
+
body?: TBody;
|
|
171
|
+
}
|
|
172
|
+
interface IBatchRequestPayload {
|
|
173
|
+
requests: IBatchRequestItem[];
|
|
161
174
|
}
|
|
162
175
|
/**
|
|
163
176
|
* Creates a Request object from the IPC event data.
|
|
164
177
|
* This function extracts the necessary information from the IPC event and constructs a Request instance.
|
|
165
178
|
*/
|
|
166
|
-
interface IResponse<
|
|
179
|
+
interface IResponse<TBody = unknown> {
|
|
167
180
|
requestId: string;
|
|
168
181
|
status: number;
|
|
169
|
-
body?:
|
|
182
|
+
body?: TBody;
|
|
170
183
|
error?: string;
|
|
184
|
+
stack?: string;
|
|
185
|
+
}
|
|
186
|
+
interface IBatchResponsePayload {
|
|
187
|
+
responses: IResponse[];
|
|
171
188
|
}
|
|
172
189
|
|
|
173
190
|
|
|
@@ -290,6 +307,10 @@ declare class Router {
|
|
|
290
307
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
291
308
|
*/
|
|
292
309
|
handle(request: Request): Promise<IResponse>;
|
|
310
|
+
private handleAtomic;
|
|
311
|
+
private handleBatch;
|
|
312
|
+
private normalizeBatchPayload;
|
|
313
|
+
private normalizeBatchItem;
|
|
293
314
|
/**
|
|
294
315
|
* Finds the route definition for a given request.
|
|
295
316
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -614,6 +635,7 @@ declare namespace Logger {
|
|
|
614
635
|
* @param args The arguments to log.
|
|
615
636
|
*/
|
|
616
637
|
function error(...args: any[]): void;
|
|
638
|
+
function errorStack(...args: any[]): void;
|
|
617
639
|
/**
|
|
618
640
|
* Logs a message to the console with log level DEBUG.
|
|
619
641
|
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
@@ -648,4 +670,4 @@ declare namespace Logger {
|
|
|
648
670
|
};
|
|
649
671
|
}
|
|
650
672
|
|
|
651
|
-
export { AppInjector, Authorize, BadGatewayException, BadRequestException, CONTROLLER_METADATA_KEY, ConflictException, Controller, type ControllerAction, Delete, ForbiddenException, GatewayTimeoutException, Get, type HttpMethod, HttpVersionNotSupportedException, type IApp, type IBinding, type IControllerMetadata, type IGuard, type IMiddleware, type IModuleMetadata, INJECTABLE_METADATA_KEY, type IRequest, type IResponse, type IRouteDefinition, type IRouteMetadata, Injectable, InsufficientStorageException, InternalServerException, type Lifetime, type LogLevel, Logger, LoopDetectedException, MODULE_METADATA_KEY, type MaybeAsync, MethodNotAllowedException, Module, NetworkAuthenticationRequiredException, NetworkConnectTimeoutException, type NextFunction, NotAcceptableException, NotExtendedException, NotFoundException, NotImplementedException, NoxApp, Patch, PaymentRequiredException, Post, Put, ROUTE_METADATA_KEY, Request, RequestTimeoutException, ResponseException, RootInjector, Router, ServiceUnavailableException, TooManyRequestsException, type Type, UnauthorizedException, UpgradeRequiredException, UseMiddlewares, VariantAlsoNegotiatesException, bootstrapApplication, getControllerMetadata, getGuardForController, getGuardForControllerAction, getInjectableMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata, getRouteMetadata, inject };
|
|
673
|
+
export { AppInjector, type AtomicHttpMethod, Authorize, BadGatewayException, BadRequestException, CONTROLLER_METADATA_KEY, ConflictException, Controller, type ControllerAction, Delete, ForbiddenException, GatewayTimeoutException, Get, type HttpMethod, HttpVersionNotSupportedException, type IApp, type IBatchRequestItem, type IBatchRequestPayload, type IBatchResponsePayload, type IBinding, type IControllerMetadata, type IGuard, type IMiddleware, type IModuleMetadata, INJECTABLE_METADATA_KEY, type IRequest, type IResponse, type IRouteDefinition, type IRouteMetadata, Injectable, InsufficientStorageException, InternalServerException, type Lifetime, type LogLevel, Logger, LoopDetectedException, MODULE_METADATA_KEY, type MaybeAsync, MethodNotAllowedException, Module, NetworkAuthenticationRequiredException, NetworkConnectTimeoutException, type NextFunction, NotAcceptableException, NotExtendedException, NotFoundException, NotImplementedException, NoxApp, Patch, PaymentRequiredException, Post, Put, ROUTE_METADATA_KEY, Request, RequestTimeoutException, ResponseException, RootInjector, Router, ServiceUnavailableException, TooManyRequestsException, type Type, UnauthorizedException, UpgradeRequiredException, UseMiddlewares, VariantAlsoNegotiatesException, bootstrapApplication, getControllerMetadata, getGuardForController, getGuardForControllerAction, getInjectableMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata, getRouteMetadata, inject };
|
package/dist/noxus.d.ts
CHANGED
|
@@ -86,7 +86,11 @@ interface IRouteMetadata {
|
|
|
86
86
|
/**
|
|
87
87
|
* The different HTTP methods that can be used in the application.
|
|
88
88
|
*/
|
|
89
|
-
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
89
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'BATCH';
|
|
90
|
+
/**
|
|
91
|
+
* Atomic HTTP verbs supported by controllers. BATCH is handled at the router level only.
|
|
92
|
+
*/
|
|
93
|
+
type AtomicHttpMethod = Exclude<HttpMethod, 'BATCH'>;
|
|
90
94
|
/**
|
|
91
95
|
* Gets the route metadata for a given target class.
|
|
92
96
|
* This metadata includes the HTTP method, path, handler, and guards defined by the route decorators.
|
|
@@ -152,22 +156,35 @@ declare class Request {
|
|
|
152
156
|
* It includes properties for the sender ID, request ID, path, method, and an optional body.
|
|
153
157
|
* This interface is used to standardize the request data across the application.
|
|
154
158
|
*/
|
|
155
|
-
interface IRequest<
|
|
159
|
+
interface IRequest<TBody = unknown> {
|
|
156
160
|
senderId: number;
|
|
157
161
|
requestId: string;
|
|
158
162
|
path: string;
|
|
159
163
|
method: HttpMethod;
|
|
160
|
-
body?:
|
|
164
|
+
body?: TBody;
|
|
165
|
+
}
|
|
166
|
+
interface IBatchRequestItem<TBody = unknown> {
|
|
167
|
+
requestId?: string;
|
|
168
|
+
path: string;
|
|
169
|
+
method: AtomicHttpMethod;
|
|
170
|
+
body?: TBody;
|
|
171
|
+
}
|
|
172
|
+
interface IBatchRequestPayload {
|
|
173
|
+
requests: IBatchRequestItem[];
|
|
161
174
|
}
|
|
162
175
|
/**
|
|
163
176
|
* Creates a Request object from the IPC event data.
|
|
164
177
|
* This function extracts the necessary information from the IPC event and constructs a Request instance.
|
|
165
178
|
*/
|
|
166
|
-
interface IResponse<
|
|
179
|
+
interface IResponse<TBody = unknown> {
|
|
167
180
|
requestId: string;
|
|
168
181
|
status: number;
|
|
169
|
-
body?:
|
|
182
|
+
body?: TBody;
|
|
170
183
|
error?: string;
|
|
184
|
+
stack?: string;
|
|
185
|
+
}
|
|
186
|
+
interface IBatchResponsePayload {
|
|
187
|
+
responses: IResponse[];
|
|
171
188
|
}
|
|
172
189
|
|
|
173
190
|
|
|
@@ -290,6 +307,10 @@ declare class Router {
|
|
|
290
307
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
291
308
|
*/
|
|
292
309
|
handle(request: Request): Promise<IResponse>;
|
|
310
|
+
private handleAtomic;
|
|
311
|
+
private handleBatch;
|
|
312
|
+
private normalizeBatchPayload;
|
|
313
|
+
private normalizeBatchItem;
|
|
293
314
|
/**
|
|
294
315
|
* Finds the route definition for a given request.
|
|
295
316
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -614,6 +635,7 @@ declare namespace Logger {
|
|
|
614
635
|
* @param args The arguments to log.
|
|
615
636
|
*/
|
|
616
637
|
function error(...args: any[]): void;
|
|
638
|
+
function errorStack(...args: any[]): void;
|
|
617
639
|
/**
|
|
618
640
|
* Logs a message to the console with log level DEBUG.
|
|
619
641
|
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|
|
@@ -648,4 +670,4 @@ declare namespace Logger {
|
|
|
648
670
|
};
|
|
649
671
|
}
|
|
650
672
|
|
|
651
|
-
export { AppInjector, Authorize, BadGatewayException, BadRequestException, CONTROLLER_METADATA_KEY, ConflictException, Controller, type ControllerAction, Delete, ForbiddenException, GatewayTimeoutException, Get, type HttpMethod, HttpVersionNotSupportedException, type IApp, type IBinding, type IControllerMetadata, type IGuard, type IMiddleware, type IModuleMetadata, INJECTABLE_METADATA_KEY, type IRequest, type IResponse, type IRouteDefinition, type IRouteMetadata, Injectable, InsufficientStorageException, InternalServerException, type Lifetime, type LogLevel, Logger, LoopDetectedException, MODULE_METADATA_KEY, type MaybeAsync, MethodNotAllowedException, Module, NetworkAuthenticationRequiredException, NetworkConnectTimeoutException, type NextFunction, NotAcceptableException, NotExtendedException, NotFoundException, NotImplementedException, NoxApp, Patch, PaymentRequiredException, Post, Put, ROUTE_METADATA_KEY, Request, RequestTimeoutException, ResponseException, RootInjector, Router, ServiceUnavailableException, TooManyRequestsException, type Type, UnauthorizedException, UpgradeRequiredException, UseMiddlewares, VariantAlsoNegotiatesException, bootstrapApplication, getControllerMetadata, getGuardForController, getGuardForControllerAction, getInjectableMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata, getRouteMetadata, inject };
|
|
673
|
+
export { AppInjector, type AtomicHttpMethod, Authorize, BadGatewayException, BadRequestException, CONTROLLER_METADATA_KEY, ConflictException, Controller, type ControllerAction, Delete, ForbiddenException, GatewayTimeoutException, Get, type HttpMethod, HttpVersionNotSupportedException, type IApp, type IBatchRequestItem, type IBatchRequestPayload, type IBatchResponsePayload, type IBinding, type IControllerMetadata, type IGuard, type IMiddleware, type IModuleMetadata, INJECTABLE_METADATA_KEY, type IRequest, type IResponse, type IRouteDefinition, type IRouteMetadata, Injectable, InsufficientStorageException, InternalServerException, type Lifetime, type LogLevel, Logger, LoopDetectedException, MODULE_METADATA_KEY, type MaybeAsync, MethodNotAllowedException, Module, NetworkAuthenticationRequiredException, NetworkConnectTimeoutException, type NextFunction, NotAcceptableException, NotExtendedException, NotFoundException, NotImplementedException, NoxApp, Patch, PaymentRequiredException, Post, Put, ROUTE_METADATA_KEY, Request, RequestTimeoutException, ResponseException, RootInjector, Router, ServiceUnavailableException, TooManyRequestsException, type Type, UnauthorizedException, UpgradeRequiredException, UseMiddlewares, VariantAlsoNegotiatesException, bootstrapApplication, getControllerMetadata, getGuardForController, getGuardForControllerAction, getInjectableMetadata, getMiddlewaresForController, getMiddlewaresForControllerAction, getModuleMetadata, getRouteMetadata, inject };
|
package/dist/noxus.js
CHANGED
|
@@ -359,7 +359,7 @@ __name(inject, "inject");
|
|
|
359
359
|
var RootInjector = new AppInjector("root");
|
|
360
360
|
|
|
361
361
|
// src/router.ts
|
|
362
|
-
var
|
|
362
|
+
var import_reflect_metadata3 = require("reflect-metadata");
|
|
363
363
|
|
|
364
364
|
// src/decorators/guards.decorator.ts
|
|
365
365
|
function Authorize(...guardClasses) {
|
|
@@ -548,6 +548,14 @@ var logLevelRank = {
|
|
|
548
548
|
}
|
|
549
549
|
__name(error, "error");
|
|
550
550
|
Logger2.error = error;
|
|
551
|
+
function errorStack(...args) {
|
|
552
|
+
if (!canLog("error")) return;
|
|
553
|
+
const callee = getCallee();
|
|
554
|
+
const prefix = getLogPrefix(callee, "error", Logger2.colors.grey);
|
|
555
|
+
console.error(prefix, ...formattedArgs(prefix, args, Logger2.colors.grey));
|
|
556
|
+
}
|
|
557
|
+
__name(errorStack, "errorStack");
|
|
558
|
+
Logger2.errorStack = errorStack;
|
|
551
559
|
function debug(...args) {
|
|
552
560
|
if (!canLog("debug")) return;
|
|
553
561
|
const callee = getCallee();
|
|
@@ -691,6 +699,28 @@ function getMiddlewaresForControllerAction(controllerName, actionName) {
|
|
|
691
699
|
__name(getMiddlewaresForControllerAction, "getMiddlewaresForControllerAction");
|
|
692
700
|
var middlewares = /* @__PURE__ */ new Map();
|
|
693
701
|
|
|
702
|
+
// src/request.ts
|
|
703
|
+
var import_reflect_metadata2 = require("reflect-metadata");
|
|
704
|
+
var _Request = class _Request {
|
|
705
|
+
constructor(event, id, method, path, body) {
|
|
706
|
+
__publicField(this, "event");
|
|
707
|
+
__publicField(this, "id");
|
|
708
|
+
__publicField(this, "method");
|
|
709
|
+
__publicField(this, "path");
|
|
710
|
+
__publicField(this, "body");
|
|
711
|
+
__publicField(this, "context", RootInjector.createScope());
|
|
712
|
+
__publicField(this, "params", {});
|
|
713
|
+
this.event = event;
|
|
714
|
+
this.id = id;
|
|
715
|
+
this.method = method;
|
|
716
|
+
this.path = path;
|
|
717
|
+
this.body = body;
|
|
718
|
+
this.path = path.replace(/^\/|\/$/g, "");
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
__name(_Request, "Request");
|
|
722
|
+
var Request = _Request;
|
|
723
|
+
|
|
694
724
|
// src/utils/radix-tree.ts
|
|
695
725
|
var _a;
|
|
696
726
|
var RadixNode = (_a = class {
|
|
@@ -856,6 +886,17 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
856
886
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
857
887
|
}
|
|
858
888
|
__name(_ts_decorate, "_ts_decorate");
|
|
889
|
+
var ATOMIC_HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
890
|
+
"GET",
|
|
891
|
+
"POST",
|
|
892
|
+
"PUT",
|
|
893
|
+
"PATCH",
|
|
894
|
+
"DELETE"
|
|
895
|
+
]);
|
|
896
|
+
function isAtomicHttpMethod(method) {
|
|
897
|
+
return typeof method === "string" && ATOMIC_HTTP_METHODS.has(method);
|
|
898
|
+
}
|
|
899
|
+
__name(isAtomicHttpMethod, "isAtomicHttpMethod");
|
|
859
900
|
var _Router = class _Router {
|
|
860
901
|
constructor() {
|
|
861
902
|
__publicField(this, "routes", new RadixTree());
|
|
@@ -924,13 +965,18 @@ var _Router = class _Router {
|
|
|
924
965
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
925
966
|
*/
|
|
926
967
|
async handle(request) {
|
|
968
|
+
if (request.method === "BATCH") {
|
|
969
|
+
return this.handleBatch(request);
|
|
970
|
+
}
|
|
971
|
+
return this.handleAtomic(request);
|
|
972
|
+
}
|
|
973
|
+
async handleAtomic(request) {
|
|
927
974
|
Logger.comment(`> ${request.method} /${request.path}`);
|
|
928
975
|
const t0 = performance.now();
|
|
929
976
|
const response = {
|
|
930
977
|
requestId: request.id,
|
|
931
978
|
status: 200,
|
|
932
|
-
body: null
|
|
933
|
-
error: void 0
|
|
979
|
+
body: null
|
|
934
980
|
};
|
|
935
981
|
try {
|
|
936
982
|
const routeDef = this.findRoute(request);
|
|
@@ -939,15 +985,68 @@ var _Router = class _Router {
|
|
|
939
985
|
throw new ResponseException(response.status, response.error);
|
|
940
986
|
}
|
|
941
987
|
} catch (error) {
|
|
988
|
+
response.body = void 0;
|
|
989
|
+
if (error instanceof ResponseException) {
|
|
990
|
+
response.status = error.status;
|
|
991
|
+
response.error = error.message;
|
|
992
|
+
response.stack = error.stack;
|
|
993
|
+
} else if (error instanceof Error) {
|
|
994
|
+
response.status = 500;
|
|
995
|
+
response.error = error.message || "Internal Server Error";
|
|
996
|
+
response.stack = error.stack || "No stack trace available";
|
|
997
|
+
} else {
|
|
998
|
+
response.status = 500;
|
|
999
|
+
response.error = "Unknown error occurred";
|
|
1000
|
+
response.stack = "No stack trace available";
|
|
1001
|
+
}
|
|
1002
|
+
} finally {
|
|
1003
|
+
const t1 = performance.now();
|
|
1004
|
+
const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
|
|
1005
|
+
if (response.status < 400) Logger.log(message);
|
|
1006
|
+
else if (response.status < 500) Logger.warn(message);
|
|
1007
|
+
else Logger.error(message);
|
|
1008
|
+
if (response.error !== void 0) {
|
|
1009
|
+
Logger.error(response.error);
|
|
1010
|
+
if (response.stack !== void 0) {
|
|
1011
|
+
Logger.errorStack(response.stack);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return response;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
async handleBatch(request) {
|
|
1018
|
+
Logger.comment(`> ${request.method} /${request.path}`);
|
|
1019
|
+
const t0 = performance.now();
|
|
1020
|
+
const response = {
|
|
1021
|
+
requestId: request.id,
|
|
1022
|
+
status: 200,
|
|
1023
|
+
body: {
|
|
1024
|
+
responses: []
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
try {
|
|
1028
|
+
const payload = this.normalizeBatchPayload(request.body);
|
|
1029
|
+
const batchResponses = [];
|
|
1030
|
+
for (const [index, item] of payload.requests.entries()) {
|
|
1031
|
+
const subRequestId = item.requestId ?? `${request.id}:${index}`;
|
|
1032
|
+
const atomicRequest = new Request(request.event, subRequestId, item.method, item.path, item.body);
|
|
1033
|
+
batchResponses.push(await this.handleAtomic(atomicRequest));
|
|
1034
|
+
}
|
|
1035
|
+
response.body.responses = batchResponses;
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
response.body = void 0;
|
|
942
1038
|
if (error instanceof ResponseException) {
|
|
943
1039
|
response.status = error.status;
|
|
944
1040
|
response.error = error.message;
|
|
1041
|
+
response.stack = error.stack;
|
|
945
1042
|
} else if (error instanceof Error) {
|
|
946
1043
|
response.status = 500;
|
|
947
1044
|
response.error = error.message || "Internal Server Error";
|
|
1045
|
+
response.stack = error.stack || "No stack trace available";
|
|
948
1046
|
} else {
|
|
949
1047
|
response.status = 500;
|
|
950
1048
|
response.error = "Unknown error occurred";
|
|
1049
|
+
response.stack = "No stack trace available";
|
|
951
1050
|
}
|
|
952
1051
|
} finally {
|
|
953
1052
|
const t1 = performance.now();
|
|
@@ -957,10 +1056,52 @@ var _Router = class _Router {
|
|
|
957
1056
|
else Logger.error(message);
|
|
958
1057
|
if (response.error !== void 0) {
|
|
959
1058
|
Logger.error(response.error);
|
|
1059
|
+
if (response.stack !== void 0) {
|
|
1060
|
+
Logger.errorStack(response.stack);
|
|
1061
|
+
}
|
|
960
1062
|
}
|
|
961
1063
|
return response;
|
|
962
1064
|
}
|
|
963
1065
|
}
|
|
1066
|
+
normalizeBatchPayload(body) {
|
|
1067
|
+
if (body === null || typeof body !== "object") {
|
|
1068
|
+
throw new BadRequestException("Batch payload must be an object containing a requests array.");
|
|
1069
|
+
}
|
|
1070
|
+
const possiblePayload = body;
|
|
1071
|
+
const { requests } = possiblePayload;
|
|
1072
|
+
if (!Array.isArray(requests)) {
|
|
1073
|
+
throw new BadRequestException("Batch payload must define a requests array.");
|
|
1074
|
+
}
|
|
1075
|
+
const normalizedRequests = requests.map((entry, index) => this.normalizeBatchItem(entry, index));
|
|
1076
|
+
return {
|
|
1077
|
+
requests: normalizedRequests
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
normalizeBatchItem(entry, index) {
|
|
1081
|
+
if (entry === null || typeof entry !== "object") {
|
|
1082
|
+
throw new BadRequestException(`Batch request at index ${index} must be an object.`);
|
|
1083
|
+
}
|
|
1084
|
+
const { requestId, path, method, body } = entry;
|
|
1085
|
+
if (requestId !== void 0 && typeof requestId !== "string") {
|
|
1086
|
+
throw new BadRequestException(`Batch request at index ${index} has an invalid requestId.`);
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1089
|
+
throw new BadRequestException(`Batch request at index ${index} must define a non-empty path.`);
|
|
1090
|
+
}
|
|
1091
|
+
if (typeof method !== "string") {
|
|
1092
|
+
throw new BadRequestException(`Batch request at index ${index} must define an HTTP method.`);
|
|
1093
|
+
}
|
|
1094
|
+
const normalizedMethod = method.toUpperCase();
|
|
1095
|
+
if (!isAtomicHttpMethod(normalizedMethod)) {
|
|
1096
|
+
throw new BadRequestException(`Batch request at index ${index} uses the unsupported method ${method}.`);
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
requestId,
|
|
1100
|
+
path,
|
|
1101
|
+
method: normalizedMethod,
|
|
1102
|
+
body
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
964
1105
|
/**
|
|
965
1106
|
* Finds the route definition for a given request.
|
|
966
1107
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -1097,30 +1238,6 @@ Router = _ts_decorate([
|
|
|
1097
1238
|
|
|
1098
1239
|
// src/app.ts
|
|
1099
1240
|
var import_main = require("electron/main");
|
|
1100
|
-
|
|
1101
|
-
// src/request.ts
|
|
1102
|
-
var import_reflect_metadata3 = require("reflect-metadata");
|
|
1103
|
-
var _Request = class _Request {
|
|
1104
|
-
constructor(event, id, method, path, body) {
|
|
1105
|
-
__publicField(this, "event");
|
|
1106
|
-
__publicField(this, "id");
|
|
1107
|
-
__publicField(this, "method");
|
|
1108
|
-
__publicField(this, "path");
|
|
1109
|
-
__publicField(this, "body");
|
|
1110
|
-
__publicField(this, "context", RootInjector.createScope());
|
|
1111
|
-
__publicField(this, "params", {});
|
|
1112
|
-
this.event = event;
|
|
1113
|
-
this.id = id;
|
|
1114
|
-
this.method = method;
|
|
1115
|
-
this.path = path;
|
|
1116
|
-
this.body = body;
|
|
1117
|
-
this.path = path.replace(/^\/|\/$/g, "");
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
__name(_Request, "Request");
|
|
1121
|
-
var Request = _Request;
|
|
1122
|
-
|
|
1123
|
-
// src/app.ts
|
|
1124
1241
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1125
1242
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1126
1243
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
package/dist/noxus.mjs
CHANGED
|
@@ -471,6 +471,14 @@ var logLevelRank = {
|
|
|
471
471
|
}
|
|
472
472
|
__name(error, "error");
|
|
473
473
|
Logger2.error = error;
|
|
474
|
+
function errorStack(...args) {
|
|
475
|
+
if (!canLog("error")) return;
|
|
476
|
+
const callee = getCallee();
|
|
477
|
+
const prefix = getLogPrefix(callee, "error", Logger2.colors.grey);
|
|
478
|
+
console.error(prefix, ...formattedArgs(prefix, args, Logger2.colors.grey));
|
|
479
|
+
}
|
|
480
|
+
__name(errorStack, "errorStack");
|
|
481
|
+
Logger2.errorStack = errorStack;
|
|
474
482
|
function debug(...args) {
|
|
475
483
|
if (!canLog("debug")) return;
|
|
476
484
|
const callee = getCallee();
|
|
@@ -614,6 +622,28 @@ function getMiddlewaresForControllerAction(controllerName, actionName) {
|
|
|
614
622
|
__name(getMiddlewaresForControllerAction, "getMiddlewaresForControllerAction");
|
|
615
623
|
var middlewares = /* @__PURE__ */ new Map();
|
|
616
624
|
|
|
625
|
+
// src/request.ts
|
|
626
|
+
import "reflect-metadata";
|
|
627
|
+
var _Request = class _Request {
|
|
628
|
+
constructor(event, id, method, path, body) {
|
|
629
|
+
__publicField(this, "event");
|
|
630
|
+
__publicField(this, "id");
|
|
631
|
+
__publicField(this, "method");
|
|
632
|
+
__publicField(this, "path");
|
|
633
|
+
__publicField(this, "body");
|
|
634
|
+
__publicField(this, "context", RootInjector.createScope());
|
|
635
|
+
__publicField(this, "params", {});
|
|
636
|
+
this.event = event;
|
|
637
|
+
this.id = id;
|
|
638
|
+
this.method = method;
|
|
639
|
+
this.path = path;
|
|
640
|
+
this.body = body;
|
|
641
|
+
this.path = path.replace(/^\/|\/$/g, "");
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
__name(_Request, "Request");
|
|
645
|
+
var Request = _Request;
|
|
646
|
+
|
|
617
647
|
// src/utils/radix-tree.ts
|
|
618
648
|
var _a;
|
|
619
649
|
var RadixNode = (_a = class {
|
|
@@ -779,6 +809,17 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
779
809
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
780
810
|
}
|
|
781
811
|
__name(_ts_decorate, "_ts_decorate");
|
|
812
|
+
var ATOMIC_HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
813
|
+
"GET",
|
|
814
|
+
"POST",
|
|
815
|
+
"PUT",
|
|
816
|
+
"PATCH",
|
|
817
|
+
"DELETE"
|
|
818
|
+
]);
|
|
819
|
+
function isAtomicHttpMethod(method) {
|
|
820
|
+
return typeof method === "string" && ATOMIC_HTTP_METHODS.has(method);
|
|
821
|
+
}
|
|
822
|
+
__name(isAtomicHttpMethod, "isAtomicHttpMethod");
|
|
782
823
|
var _Router = class _Router {
|
|
783
824
|
constructor() {
|
|
784
825
|
__publicField(this, "routes", new RadixTree());
|
|
@@ -847,13 +888,18 @@ var _Router = class _Router {
|
|
|
847
888
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
848
889
|
*/
|
|
849
890
|
async handle(request) {
|
|
891
|
+
if (request.method === "BATCH") {
|
|
892
|
+
return this.handleBatch(request);
|
|
893
|
+
}
|
|
894
|
+
return this.handleAtomic(request);
|
|
895
|
+
}
|
|
896
|
+
async handleAtomic(request) {
|
|
850
897
|
Logger.comment(`> ${request.method} /${request.path}`);
|
|
851
898
|
const t0 = performance.now();
|
|
852
899
|
const response = {
|
|
853
900
|
requestId: request.id,
|
|
854
901
|
status: 200,
|
|
855
|
-
body: null
|
|
856
|
-
error: void 0
|
|
902
|
+
body: null
|
|
857
903
|
};
|
|
858
904
|
try {
|
|
859
905
|
const routeDef = this.findRoute(request);
|
|
@@ -862,15 +908,68 @@ var _Router = class _Router {
|
|
|
862
908
|
throw new ResponseException(response.status, response.error);
|
|
863
909
|
}
|
|
864
910
|
} catch (error) {
|
|
911
|
+
response.body = void 0;
|
|
912
|
+
if (error instanceof ResponseException) {
|
|
913
|
+
response.status = error.status;
|
|
914
|
+
response.error = error.message;
|
|
915
|
+
response.stack = error.stack;
|
|
916
|
+
} else if (error instanceof Error) {
|
|
917
|
+
response.status = 500;
|
|
918
|
+
response.error = error.message || "Internal Server Error";
|
|
919
|
+
response.stack = error.stack || "No stack trace available";
|
|
920
|
+
} else {
|
|
921
|
+
response.status = 500;
|
|
922
|
+
response.error = "Unknown error occurred";
|
|
923
|
+
response.stack = "No stack trace available";
|
|
924
|
+
}
|
|
925
|
+
} finally {
|
|
926
|
+
const t1 = performance.now();
|
|
927
|
+
const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
|
|
928
|
+
if (response.status < 400) Logger.log(message);
|
|
929
|
+
else if (response.status < 500) Logger.warn(message);
|
|
930
|
+
else Logger.error(message);
|
|
931
|
+
if (response.error !== void 0) {
|
|
932
|
+
Logger.error(response.error);
|
|
933
|
+
if (response.stack !== void 0) {
|
|
934
|
+
Logger.errorStack(response.stack);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return response;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
async handleBatch(request) {
|
|
941
|
+
Logger.comment(`> ${request.method} /${request.path}`);
|
|
942
|
+
const t0 = performance.now();
|
|
943
|
+
const response = {
|
|
944
|
+
requestId: request.id,
|
|
945
|
+
status: 200,
|
|
946
|
+
body: {
|
|
947
|
+
responses: []
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
try {
|
|
951
|
+
const payload = this.normalizeBatchPayload(request.body);
|
|
952
|
+
const batchResponses = [];
|
|
953
|
+
for (const [index, item] of payload.requests.entries()) {
|
|
954
|
+
const subRequestId = item.requestId ?? `${request.id}:${index}`;
|
|
955
|
+
const atomicRequest = new Request(request.event, subRequestId, item.method, item.path, item.body);
|
|
956
|
+
batchResponses.push(await this.handleAtomic(atomicRequest));
|
|
957
|
+
}
|
|
958
|
+
response.body.responses = batchResponses;
|
|
959
|
+
} catch (error) {
|
|
960
|
+
response.body = void 0;
|
|
865
961
|
if (error instanceof ResponseException) {
|
|
866
962
|
response.status = error.status;
|
|
867
963
|
response.error = error.message;
|
|
964
|
+
response.stack = error.stack;
|
|
868
965
|
} else if (error instanceof Error) {
|
|
869
966
|
response.status = 500;
|
|
870
967
|
response.error = error.message || "Internal Server Error";
|
|
968
|
+
response.stack = error.stack || "No stack trace available";
|
|
871
969
|
} else {
|
|
872
970
|
response.status = 500;
|
|
873
971
|
response.error = "Unknown error occurred";
|
|
972
|
+
response.stack = "No stack trace available";
|
|
874
973
|
}
|
|
875
974
|
} finally {
|
|
876
975
|
const t1 = performance.now();
|
|
@@ -880,10 +979,52 @@ var _Router = class _Router {
|
|
|
880
979
|
else Logger.error(message);
|
|
881
980
|
if (response.error !== void 0) {
|
|
882
981
|
Logger.error(response.error);
|
|
982
|
+
if (response.stack !== void 0) {
|
|
983
|
+
Logger.errorStack(response.stack);
|
|
984
|
+
}
|
|
883
985
|
}
|
|
884
986
|
return response;
|
|
885
987
|
}
|
|
886
988
|
}
|
|
989
|
+
normalizeBatchPayload(body) {
|
|
990
|
+
if (body === null || typeof body !== "object") {
|
|
991
|
+
throw new BadRequestException("Batch payload must be an object containing a requests array.");
|
|
992
|
+
}
|
|
993
|
+
const possiblePayload = body;
|
|
994
|
+
const { requests } = possiblePayload;
|
|
995
|
+
if (!Array.isArray(requests)) {
|
|
996
|
+
throw new BadRequestException("Batch payload must define a requests array.");
|
|
997
|
+
}
|
|
998
|
+
const normalizedRequests = requests.map((entry, index) => this.normalizeBatchItem(entry, index));
|
|
999
|
+
return {
|
|
1000
|
+
requests: normalizedRequests
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
normalizeBatchItem(entry, index) {
|
|
1004
|
+
if (entry === null || typeof entry !== "object") {
|
|
1005
|
+
throw new BadRequestException(`Batch request at index ${index} must be an object.`);
|
|
1006
|
+
}
|
|
1007
|
+
const { requestId, path, method, body } = entry;
|
|
1008
|
+
if (requestId !== void 0 && typeof requestId !== "string") {
|
|
1009
|
+
throw new BadRequestException(`Batch request at index ${index} has an invalid requestId.`);
|
|
1010
|
+
}
|
|
1011
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1012
|
+
throw new BadRequestException(`Batch request at index ${index} must define a non-empty path.`);
|
|
1013
|
+
}
|
|
1014
|
+
if (typeof method !== "string") {
|
|
1015
|
+
throw new BadRequestException(`Batch request at index ${index} must define an HTTP method.`);
|
|
1016
|
+
}
|
|
1017
|
+
const normalizedMethod = method.toUpperCase();
|
|
1018
|
+
if (!isAtomicHttpMethod(normalizedMethod)) {
|
|
1019
|
+
throw new BadRequestException(`Batch request at index ${index} uses the unsupported method ${method}.`);
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
requestId,
|
|
1023
|
+
path,
|
|
1024
|
+
method: normalizedMethod,
|
|
1025
|
+
body
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
887
1028
|
/**
|
|
888
1029
|
* Finds the route definition for a given request.
|
|
889
1030
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -1020,30 +1161,6 @@ Router = _ts_decorate([
|
|
|
1020
1161
|
|
|
1021
1162
|
// src/app.ts
|
|
1022
1163
|
import { app, BrowserWindow, ipcMain, MessageChannelMain } from "electron/main";
|
|
1023
|
-
|
|
1024
|
-
// src/request.ts
|
|
1025
|
-
import "reflect-metadata";
|
|
1026
|
-
var _Request = class _Request {
|
|
1027
|
-
constructor(event, id, method, path, body) {
|
|
1028
|
-
__publicField(this, "event");
|
|
1029
|
-
__publicField(this, "id");
|
|
1030
|
-
__publicField(this, "method");
|
|
1031
|
-
__publicField(this, "path");
|
|
1032
|
-
__publicField(this, "body");
|
|
1033
|
-
__publicField(this, "context", RootInjector.createScope());
|
|
1034
|
-
__publicField(this, "params", {});
|
|
1035
|
-
this.event = event;
|
|
1036
|
-
this.id = id;
|
|
1037
|
-
this.method = method;
|
|
1038
|
-
this.path = path;
|
|
1039
|
-
this.body = body;
|
|
1040
|
-
this.path = path.replace(/^\/|\/$/g, "");
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
__name(_Request, "Request");
|
|
1044
|
-
var Request = _Request;
|
|
1045
|
-
|
|
1046
|
-
// src/app.ts
|
|
1047
1164
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1048
1165
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1049
1166
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
package/package.json
CHANGED
|
@@ -23,7 +23,12 @@ export interface IRouteMetadata {
|
|
|
23
23
|
/**
|
|
24
24
|
* The different HTTP methods that can be used in the application.
|
|
25
25
|
*/
|
|
26
|
-
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
26
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'BATCH';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Atomic HTTP verbs supported by controllers. BATCH is handled at the router level only.
|
|
30
|
+
*/
|
|
31
|
+
export type AtomicHttpMethod = Exclude<HttpMethod, 'BATCH'>;
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
34
|
* The configuration that waits a route's decorator.
|
package/src/request.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import 'reflect-metadata';
|
|
8
|
-
import { HttpMethod } from 'src/decorators/method.decorator';
|
|
8
|
+
import { AtomicHttpMethod, HttpMethod } from 'src/decorators/method.decorator';
|
|
9
9
|
import { AppInjector, RootInjector } from 'src/DI/app-injector';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -34,21 +34,37 @@ export class Request {
|
|
|
34
34
|
* It includes properties for the sender ID, request ID, path, method, and an optional body.
|
|
35
35
|
* This interface is used to standardize the request data across the application.
|
|
36
36
|
*/
|
|
37
|
-
export interface IRequest<
|
|
37
|
+
export interface IRequest<TBody = unknown> {
|
|
38
38
|
senderId: number;
|
|
39
39
|
requestId: string;
|
|
40
40
|
path: string;
|
|
41
41
|
method: HttpMethod;
|
|
42
|
-
body?:
|
|
42
|
+
body?: TBody;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface IBatchRequestItem<TBody = unknown> {
|
|
46
|
+
requestId?: string;
|
|
47
|
+
path: string;
|
|
48
|
+
method: AtomicHttpMethod;
|
|
49
|
+
body?: TBody;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface IBatchRequestPayload {
|
|
53
|
+
requests: IBatchRequestItem[];
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
/**
|
|
46
57
|
* Creates a Request object from the IPC event data.
|
|
47
58
|
* This function extracts the necessary information from the IPC event and constructs a Request instance.
|
|
48
59
|
*/
|
|
49
|
-
export interface IResponse<
|
|
60
|
+
export interface IResponse<TBody = unknown> {
|
|
50
61
|
requestId: string;
|
|
51
62
|
status: number;
|
|
52
|
-
body?:
|
|
63
|
+
body?: TBody;
|
|
53
64
|
error?: string;
|
|
65
|
+
stack?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface IBatchResponsePayload {
|
|
69
|
+
responses: IResponse[];
|
|
54
70
|
}
|
package/src/router.ts
CHANGED
|
@@ -8,14 +8,20 @@ import 'reflect-metadata';
|
|
|
8
8
|
import { getControllerMetadata } from 'src/decorators/controller.decorator';
|
|
9
9
|
import { getGuardForController, getGuardForControllerAction, IGuard } from 'src/decorators/guards.decorator';
|
|
10
10
|
import { Injectable } from 'src/decorators/injectable.decorator';
|
|
11
|
-
import { getRouteMetadata } from 'src/decorators/method.decorator';
|
|
11
|
+
import { AtomicHttpMethod, getRouteMetadata } from 'src/decorators/method.decorator';
|
|
12
12
|
import { getMiddlewaresForController, getMiddlewaresForControllerAction, IMiddleware, NextFunction } from 'src/decorators/middleware.decorator';
|
|
13
|
-
import { MethodNotAllowedException, NotFoundException, ResponseException, UnauthorizedException } from 'src/exceptions';
|
|
14
|
-
import { IResponse, Request } from 'src/request';
|
|
13
|
+
import { BadRequestException, MethodNotAllowedException, NotFoundException, ResponseException, UnauthorizedException } from 'src/exceptions';
|
|
14
|
+
import { IBatchRequestItem, IBatchRequestPayload, IBatchResponsePayload, IResponse, Request } from 'src/request';
|
|
15
15
|
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
|
+
const ATOMIC_HTTP_METHODS: ReadonlySet<AtomicHttpMethod> = new Set<AtomicHttpMethod>(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
20
|
+
|
|
21
|
+
function isAtomicHttpMethod(method: unknown): method is AtomicHttpMethod {
|
|
22
|
+
return typeof method === 'string' && ATOMIC_HTTP_METHODS.has(method as AtomicHttpMethod);
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* IRouteDefinition interface defines the structure of a route in the application.
|
|
21
27
|
* It includes the HTTP method, path, controller class, handler method name,
|
|
@@ -121,6 +127,14 @@ export class Router {
|
|
|
121
127
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
122
128
|
*/
|
|
123
129
|
public async handle(request: Request): Promise<IResponse> {
|
|
130
|
+
if(request.method === 'BATCH') {
|
|
131
|
+
return this.handleBatch(request);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return this.handleAtomic(request);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private async handleAtomic(request: Request): Promise<IResponse> {
|
|
124
138
|
Logger.comment(`> ${request.method} /${request.path}`);
|
|
125
139
|
|
|
126
140
|
const t0 = performance.now();
|
|
@@ -129,7 +143,6 @@ export class Router {
|
|
|
129
143
|
requestId: request.id,
|
|
130
144
|
status: 200,
|
|
131
145
|
body: null,
|
|
132
|
-
error: undefined,
|
|
133
146
|
};
|
|
134
147
|
|
|
135
148
|
try {
|
|
@@ -141,17 +154,22 @@ export class Router {
|
|
|
141
154
|
}
|
|
142
155
|
}
|
|
143
156
|
catch(error: unknown) {
|
|
157
|
+
response.body = undefined;
|
|
158
|
+
|
|
144
159
|
if(error instanceof ResponseException) {
|
|
145
160
|
response.status = error.status;
|
|
146
161
|
response.error = error.message;
|
|
162
|
+
response.stack = error.stack;
|
|
147
163
|
}
|
|
148
164
|
else if(error instanceof Error) {
|
|
149
165
|
response.status = 500;
|
|
150
166
|
response.error = error.message || 'Internal Server Error';
|
|
167
|
+
response.stack = error.stack || 'No stack trace available';
|
|
151
168
|
}
|
|
152
169
|
else {
|
|
153
170
|
response.status = 500;
|
|
154
171
|
response.error = 'Unknown error occurred';
|
|
172
|
+
response.stack = 'No stack trace available';
|
|
155
173
|
}
|
|
156
174
|
}
|
|
157
175
|
finally {
|
|
@@ -168,12 +186,132 @@ export class Router {
|
|
|
168
186
|
|
|
169
187
|
if(response.error !== undefined) {
|
|
170
188
|
Logger.error(response.error);
|
|
189
|
+
|
|
190
|
+
if(response.stack !== undefined) {
|
|
191
|
+
Logger.errorStack(response.stack);
|
|
192
|
+
}
|
|
171
193
|
}
|
|
172
194
|
|
|
173
195
|
return response;
|
|
174
196
|
}
|
|
175
197
|
}
|
|
176
198
|
|
|
199
|
+
private async handleBatch(request: Request): Promise<IResponse> {
|
|
200
|
+
Logger.comment(`> ${request.method} /${request.path}`);
|
|
201
|
+
|
|
202
|
+
const t0 = performance.now();
|
|
203
|
+
|
|
204
|
+
const response: IResponse<IBatchResponsePayload> = {
|
|
205
|
+
requestId: request.id,
|
|
206
|
+
status: 200,
|
|
207
|
+
body: { responses: [] },
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const payload = this.normalizeBatchPayload(request.body);
|
|
212
|
+
const batchResponses: IResponse[] = [];
|
|
213
|
+
|
|
214
|
+
for(const [index, item] of payload.requests.entries()) {
|
|
215
|
+
const subRequestId = item.requestId ?? `${request.id}:${index}`;
|
|
216
|
+
const atomicRequest = new Request(request.event, subRequestId, item.method, item.path, item.body);
|
|
217
|
+
batchResponses.push(await this.handleAtomic(atomicRequest));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
response.body!.responses = batchResponses;
|
|
221
|
+
}
|
|
222
|
+
catch(error: unknown) {
|
|
223
|
+
response.body = undefined;
|
|
224
|
+
|
|
225
|
+
if(error instanceof ResponseException) {
|
|
226
|
+
response.status = error.status;
|
|
227
|
+
response.error = error.message;
|
|
228
|
+
response.stack = error.stack;
|
|
229
|
+
}
|
|
230
|
+
else if(error instanceof Error) {
|
|
231
|
+
response.status = 500;
|
|
232
|
+
response.error = error.message || 'Internal Server Error';
|
|
233
|
+
response.stack = error.stack || 'No stack trace available';
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
response.status = 500;
|
|
237
|
+
response.error = 'Unknown error occurred';
|
|
238
|
+
response.stack = 'No stack trace available';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
const t1 = performance.now();
|
|
243
|
+
|
|
244
|
+
const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
|
|
245
|
+
|
|
246
|
+
if(response.status < 400)
|
|
247
|
+
Logger.log(message);
|
|
248
|
+
else if(response.status < 500)
|
|
249
|
+
Logger.warn(message);
|
|
250
|
+
else
|
|
251
|
+
Logger.error(message);
|
|
252
|
+
|
|
253
|
+
if(response.error !== undefined) {
|
|
254
|
+
Logger.error(response.error);
|
|
255
|
+
|
|
256
|
+
if(response.stack !== undefined) {
|
|
257
|
+
Logger.errorStack(response.stack);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private normalizeBatchPayload(body: unknown): IBatchRequestPayload {
|
|
266
|
+
if(body === null || typeof body !== 'object') {
|
|
267
|
+
throw new BadRequestException('Batch payload must be an object containing a requests array.');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const possiblePayload = body as Partial<IBatchRequestPayload>;
|
|
271
|
+
const { requests } = possiblePayload;
|
|
272
|
+
|
|
273
|
+
if(!Array.isArray(requests)) {
|
|
274
|
+
throw new BadRequestException('Batch payload must define a requests array.');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const normalizedRequests = requests.map((entry, index) => this.normalizeBatchItem(entry, index));
|
|
278
|
+
|
|
279
|
+
return { requests: normalizedRequests };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private normalizeBatchItem(entry: unknown, index: number): IBatchRequestItem {
|
|
283
|
+
if(entry === null || typeof entry !== 'object') {
|
|
284
|
+
throw new BadRequestException(`Batch request at index ${index} must be an object.`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { requestId, path, method, body } = entry as Partial<IBatchRequestItem> & { method?: unknown };
|
|
288
|
+
|
|
289
|
+
if(requestId !== undefined && typeof requestId !== 'string') {
|
|
290
|
+
throw new BadRequestException(`Batch request at index ${index} has an invalid requestId.`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if(typeof path !== 'string' || path.length === 0) {
|
|
294
|
+
throw new BadRequestException(`Batch request at index ${index} must define a non-empty path.`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if(typeof method !== 'string') {
|
|
298
|
+
throw new BadRequestException(`Batch request at index ${index} must define an HTTP method.`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const normalizedMethod = method.toUpperCase();
|
|
302
|
+
|
|
303
|
+
if(!isAtomicHttpMethod(normalizedMethod)) {
|
|
304
|
+
throw new BadRequestException(`Batch request at index ${index} uses the unsupported method ${method}.`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
requestId,
|
|
309
|
+
path,
|
|
310
|
+
method: normalizedMethod as AtomicHttpMethod,
|
|
311
|
+
body,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
177
315
|
/**
|
|
178
316
|
* Finds the route definition for a given request.
|
|
179
317
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
package/src/utils/logger.ts
CHANGED
|
@@ -188,6 +188,15 @@ export namespace Logger {
|
|
|
188
188
|
console.error(prefix, ...formattedArgs(prefix, args, colors.red));
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
export function errorStack(...args: any[]): void {
|
|
192
|
+
if(!canLog('error'))
|
|
193
|
+
return;
|
|
194
|
+
|
|
195
|
+
const callee = getCallee();
|
|
196
|
+
const prefix = getLogPrefix(callee, "error", colors.grey);
|
|
197
|
+
console.error(prefix, ...formattedArgs(prefix, args, colors.grey));
|
|
198
|
+
}
|
|
199
|
+
|
|
191
200
|
/**
|
|
192
201
|
* Logs a message to the console with log level DEBUG.
|
|
193
202
|
* This function formats the message with a timestamp, process ID, and the name of the caller function or class.
|