@noxfly/noxus 1.1.9 → 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 +26 -6
- package/dist/noxus.d.ts +26 -6
- package/dist/noxus.js +128 -25
- package/dist/noxus.mjs +127 -24
- package/package.json +1 -1
- package/src/decorators/method.decorator.ts +6 -1
- package/src/request.ts +20 -5
- package/src/router.ts +133 -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,24 +156,36 @@ 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;
|
|
171
184
|
stack?: string;
|
|
172
185
|
}
|
|
186
|
+
interface IBatchResponsePayload {
|
|
187
|
+
responses: IResponse[];
|
|
188
|
+
}
|
|
173
189
|
|
|
174
190
|
|
|
175
191
|
/**
|
|
@@ -291,6 +307,10 @@ declare class Router {
|
|
|
291
307
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
292
308
|
*/
|
|
293
309
|
handle(request: Request): Promise<IResponse>;
|
|
310
|
+
private handleAtomic;
|
|
311
|
+
private handleBatch;
|
|
312
|
+
private normalizeBatchPayload;
|
|
313
|
+
private normalizeBatchItem;
|
|
294
314
|
/**
|
|
295
315
|
* Finds the route definition for a given request.
|
|
296
316
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -650,4 +670,4 @@ declare namespace Logger {
|
|
|
650
670
|
};
|
|
651
671
|
}
|
|
652
672
|
|
|
653
|
-
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,24 +156,36 @@ 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;
|
|
171
184
|
stack?: string;
|
|
172
185
|
}
|
|
186
|
+
interface IBatchResponsePayload {
|
|
187
|
+
responses: IResponse[];
|
|
188
|
+
}
|
|
173
189
|
|
|
174
190
|
|
|
175
191
|
/**
|
|
@@ -291,6 +307,10 @@ declare class Router {
|
|
|
291
307
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
292
308
|
*/
|
|
293
309
|
handle(request: Request): Promise<IResponse>;
|
|
310
|
+
private handleAtomic;
|
|
311
|
+
private handleBatch;
|
|
312
|
+
private normalizeBatchPayload;
|
|
313
|
+
private normalizeBatchItem;
|
|
294
314
|
/**
|
|
295
315
|
* Finds the route definition for a given request.
|
|
296
316
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -650,4 +670,4 @@ declare namespace Logger {
|
|
|
650
670
|
};
|
|
651
671
|
}
|
|
652
672
|
|
|
653
|
-
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) {
|
|
@@ -699,6 +699,28 @@ function getMiddlewaresForControllerAction(controllerName, actionName) {
|
|
|
699
699
|
__name(getMiddlewaresForControllerAction, "getMiddlewaresForControllerAction");
|
|
700
700
|
var middlewares = /* @__PURE__ */ new Map();
|
|
701
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
|
+
|
|
702
724
|
// src/utils/radix-tree.ts
|
|
703
725
|
var _a;
|
|
704
726
|
var RadixNode = (_a = class {
|
|
@@ -864,6 +886,17 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
864
886
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
865
887
|
}
|
|
866
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");
|
|
867
900
|
var _Router = class _Router {
|
|
868
901
|
constructor() {
|
|
869
902
|
__publicField(this, "routes", new RadixTree());
|
|
@@ -932,6 +965,12 @@ var _Router = class _Router {
|
|
|
932
965
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
933
966
|
*/
|
|
934
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) {
|
|
935
974
|
Logger.comment(`> ${request.method} /${request.path}`);
|
|
936
975
|
const t0 = performance.now();
|
|
937
976
|
const response = {
|
|
@@ -975,6 +1014,94 @@ var _Router = class _Router {
|
|
|
975
1014
|
return response;
|
|
976
1015
|
}
|
|
977
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;
|
|
1038
|
+
if (error instanceof ResponseException) {
|
|
1039
|
+
response.status = error.status;
|
|
1040
|
+
response.error = error.message;
|
|
1041
|
+
response.stack = error.stack;
|
|
1042
|
+
} else if (error instanceof Error) {
|
|
1043
|
+
response.status = 500;
|
|
1044
|
+
response.error = error.message || "Internal Server Error";
|
|
1045
|
+
response.stack = error.stack || "No stack trace available";
|
|
1046
|
+
} else {
|
|
1047
|
+
response.status = 500;
|
|
1048
|
+
response.error = "Unknown error occurred";
|
|
1049
|
+
response.stack = "No stack trace available";
|
|
1050
|
+
}
|
|
1051
|
+
} finally {
|
|
1052
|
+
const t1 = performance.now();
|
|
1053
|
+
const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
|
|
1054
|
+
if (response.status < 400) Logger.log(message);
|
|
1055
|
+
else if (response.status < 500) Logger.warn(message);
|
|
1056
|
+
else Logger.error(message);
|
|
1057
|
+
if (response.error !== void 0) {
|
|
1058
|
+
Logger.error(response.error);
|
|
1059
|
+
if (response.stack !== void 0) {
|
|
1060
|
+
Logger.errorStack(response.stack);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return response;
|
|
1064
|
+
}
|
|
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
|
+
}
|
|
978
1105
|
/**
|
|
979
1106
|
* Finds the route definition for a given request.
|
|
980
1107
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -1111,30 +1238,6 @@ Router = _ts_decorate([
|
|
|
1111
1238
|
|
|
1112
1239
|
// src/app.ts
|
|
1113
1240
|
var import_main = require("electron/main");
|
|
1114
|
-
|
|
1115
|
-
// src/request.ts
|
|
1116
|
-
var import_reflect_metadata3 = require("reflect-metadata");
|
|
1117
|
-
var _Request = class _Request {
|
|
1118
|
-
constructor(event, id, method, path, body) {
|
|
1119
|
-
__publicField(this, "event");
|
|
1120
|
-
__publicField(this, "id");
|
|
1121
|
-
__publicField(this, "method");
|
|
1122
|
-
__publicField(this, "path");
|
|
1123
|
-
__publicField(this, "body");
|
|
1124
|
-
__publicField(this, "context", RootInjector.createScope());
|
|
1125
|
-
__publicField(this, "params", {});
|
|
1126
|
-
this.event = event;
|
|
1127
|
-
this.id = id;
|
|
1128
|
-
this.method = method;
|
|
1129
|
-
this.path = path;
|
|
1130
|
-
this.body = body;
|
|
1131
|
-
this.path = path.replace(/^\/|\/$/g, "");
|
|
1132
|
-
}
|
|
1133
|
-
};
|
|
1134
|
-
__name(_Request, "Request");
|
|
1135
|
-
var Request = _Request;
|
|
1136
|
-
|
|
1137
|
-
// src/app.ts
|
|
1138
1241
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1139
1242
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1140
1243
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
package/dist/noxus.mjs
CHANGED
|
@@ -622,6 +622,28 @@ function getMiddlewaresForControllerAction(controllerName, actionName) {
|
|
|
622
622
|
__name(getMiddlewaresForControllerAction, "getMiddlewaresForControllerAction");
|
|
623
623
|
var middlewares = /* @__PURE__ */ new Map();
|
|
624
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
|
+
|
|
625
647
|
// src/utils/radix-tree.ts
|
|
626
648
|
var _a;
|
|
627
649
|
var RadixNode = (_a = class {
|
|
@@ -787,6 +809,17 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
787
809
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
788
810
|
}
|
|
789
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");
|
|
790
823
|
var _Router = class _Router {
|
|
791
824
|
constructor() {
|
|
792
825
|
__publicField(this, "routes", new RadixTree());
|
|
@@ -855,6 +888,12 @@ var _Router = class _Router {
|
|
|
855
888
|
* @param channelSenderId - The ID of the sender channel to shut down.
|
|
856
889
|
*/
|
|
857
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) {
|
|
858
897
|
Logger.comment(`> ${request.method} /${request.path}`);
|
|
859
898
|
const t0 = performance.now();
|
|
860
899
|
const response = {
|
|
@@ -898,6 +937,94 @@ var _Router = class _Router {
|
|
|
898
937
|
return response;
|
|
899
938
|
}
|
|
900
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;
|
|
961
|
+
if (error instanceof ResponseException) {
|
|
962
|
+
response.status = error.status;
|
|
963
|
+
response.error = error.message;
|
|
964
|
+
response.stack = error.stack;
|
|
965
|
+
} else if (error instanceof Error) {
|
|
966
|
+
response.status = 500;
|
|
967
|
+
response.error = error.message || "Internal Server Error";
|
|
968
|
+
response.stack = error.stack || "No stack trace available";
|
|
969
|
+
} else {
|
|
970
|
+
response.status = 500;
|
|
971
|
+
response.error = "Unknown error occurred";
|
|
972
|
+
response.stack = "No stack trace available";
|
|
973
|
+
}
|
|
974
|
+
} finally {
|
|
975
|
+
const t1 = performance.now();
|
|
976
|
+
const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
|
|
977
|
+
if (response.status < 400) Logger.log(message);
|
|
978
|
+
else if (response.status < 500) Logger.warn(message);
|
|
979
|
+
else Logger.error(message);
|
|
980
|
+
if (response.error !== void 0) {
|
|
981
|
+
Logger.error(response.error);
|
|
982
|
+
if (response.stack !== void 0) {
|
|
983
|
+
Logger.errorStack(response.stack);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return response;
|
|
987
|
+
}
|
|
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
|
+
}
|
|
901
1028
|
/**
|
|
902
1029
|
* Finds the route definition for a given request.
|
|
903
1030
|
* This method searches the routing tree for a matching route based on the request's path and method.
|
|
@@ -1034,30 +1161,6 @@ Router = _ts_decorate([
|
|
|
1034
1161
|
|
|
1035
1162
|
// src/app.ts
|
|
1036
1163
|
import { app, BrowserWindow, ipcMain, MessageChannelMain } from "electron/main";
|
|
1037
|
-
|
|
1038
|
-
// src/request.ts
|
|
1039
|
-
import "reflect-metadata";
|
|
1040
|
-
var _Request = class _Request {
|
|
1041
|
-
constructor(event, id, method, path, body) {
|
|
1042
|
-
__publicField(this, "event");
|
|
1043
|
-
__publicField(this, "id");
|
|
1044
|
-
__publicField(this, "method");
|
|
1045
|
-
__publicField(this, "path");
|
|
1046
|
-
__publicField(this, "body");
|
|
1047
|
-
__publicField(this, "context", RootInjector.createScope());
|
|
1048
|
-
__publicField(this, "params", {});
|
|
1049
|
-
this.event = event;
|
|
1050
|
-
this.id = id;
|
|
1051
|
-
this.method = method;
|
|
1052
|
-
this.path = path;
|
|
1053
|
-
this.body = body;
|
|
1054
|
-
this.path = path.replace(/^\/|\/$/g, "");
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1057
|
-
__name(_Request, "Request");
|
|
1058
|
-
var Request = _Request;
|
|
1059
|
-
|
|
1060
|
-
// src/app.ts
|
|
1061
1164
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1062
1165
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1063
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,22 +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;
|
|
54
65
|
stack?: string;
|
|
55
66
|
}
|
|
67
|
+
|
|
68
|
+
export interface IBatchResponsePayload {
|
|
69
|
+
responses: IResponse[];
|
|
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();
|
|
@@ -182,6 +196,122 @@ export class Router {
|
|
|
182
196
|
}
|
|
183
197
|
}
|
|
184
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
|
+
|
|
185
315
|
/**
|
|
186
316
|
* Finds the route definition for a given request.
|
|
187
317
|
* This method searches the routing tree for a matching route based on the request's path and method.
|