@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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "typescript.tsdk": "node_modules\\typescript\\lib"
3
+ }
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<T = any> {
159
+ interface IRequest<TBody = unknown> {
156
160
  senderId: number;
157
161
  requestId: string;
158
162
  path: string;
159
163
  method: HttpMethod;
160
- body?: T;
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<T = any> {
179
+ interface IResponse<TBody = unknown> {
167
180
  requestId: string;
168
181
  status: number;
169
- body?: T;
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<T = any> {
159
+ interface IRequest<TBody = unknown> {
156
160
  senderId: number;
157
161
  requestId: string;
158
162
  path: string;
159
163
  method: HttpMethod;
160
- body?: T;
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<T = any> {
179
+ interface IResponse<TBody = unknown> {
167
180
  requestId: string;
168
181
  status: number;
169
- body?: T;
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 import_reflect_metadata2 = require("reflect-metadata");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noxfly/noxus",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "main": "dist/noxus.js",
5
5
  "types": "dist/noxus.d.ts",
6
6
  "scripts": {
@@ -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<T = any> {
37
+ export interface IRequest<TBody = unknown> {
38
38
  senderId: number;
39
39
  requestId: string;
40
40
  path: string;
41
41
  method: HttpMethod;
42
- body?: T;
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<T = any> {
60
+ export interface IResponse<TBody = unknown> {
50
61
  requestId: string;
51
62
  status: number;
52
- body?: T;
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.
@@ -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.