@noxfly/noxus 1.1.9 → 1.2.0

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.
@@ -622,6 +622,45 @@ 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
+ var RENDERER_EVENT_TYPE = "noxus:event";
647
+ function createRendererEventMessage(event, payload) {
648
+ return {
649
+ type: RENDERER_EVENT_TYPE,
650
+ event,
651
+ payload
652
+ };
653
+ }
654
+ __name(createRendererEventMessage, "createRendererEventMessage");
655
+ function isRendererEventMessage(value) {
656
+ if (value === null || typeof value !== "object") {
657
+ return false;
658
+ }
659
+ const possibleMessage = value;
660
+ return possibleMessage.type === RENDERER_EVENT_TYPE && typeof possibleMessage.event === "string";
661
+ }
662
+ __name(isRendererEventMessage, "isRendererEventMessage");
663
+
625
664
  // src/utils/radix-tree.ts
626
665
  var _a;
627
666
  var RadixNode = (_a = class {
@@ -787,6 +826,17 @@ function _ts_decorate(decorators, target, key, desc) {
787
826
  return c > 3 && r && Object.defineProperty(target, key, r), r;
788
827
  }
789
828
  __name(_ts_decorate, "_ts_decorate");
829
+ var ATOMIC_HTTP_METHODS = /* @__PURE__ */ new Set([
830
+ "GET",
831
+ "POST",
832
+ "PUT",
833
+ "PATCH",
834
+ "DELETE"
835
+ ]);
836
+ function isAtomicHttpMethod(method) {
837
+ return typeof method === "string" && ATOMIC_HTTP_METHODS.has(method);
838
+ }
839
+ __name(isAtomicHttpMethod, "isAtomicHttpMethod");
790
840
  var _Router = class _Router {
791
841
  constructor() {
792
842
  __publicField(this, "routes", new RadixTree());
@@ -855,6 +905,12 @@ var _Router = class _Router {
855
905
  * @param channelSenderId - The ID of the sender channel to shut down.
856
906
  */
857
907
  async handle(request) {
908
+ if (request.method === "BATCH") {
909
+ return this.handleBatch(request);
910
+ }
911
+ return this.handleAtomic(request);
912
+ }
913
+ async handleAtomic(request) {
858
914
  Logger.comment(`> ${request.method} /${request.path}`);
859
915
  const t0 = performance.now();
860
916
  const response = {
@@ -898,6 +954,94 @@ var _Router = class _Router {
898
954
  return response;
899
955
  }
900
956
  }
957
+ async handleBatch(request) {
958
+ Logger.comment(`> ${request.method} /${request.path}`);
959
+ const t0 = performance.now();
960
+ const response = {
961
+ requestId: request.id,
962
+ status: 200,
963
+ body: {
964
+ responses: []
965
+ }
966
+ };
967
+ try {
968
+ const payload = this.normalizeBatchPayload(request.body);
969
+ const batchResponses = [];
970
+ for (const [index, item] of payload.requests.entries()) {
971
+ const subRequestId = item.requestId ?? `${request.id}:${index}`;
972
+ const atomicRequest = new Request(request.event, subRequestId, item.method, item.path, item.body);
973
+ batchResponses.push(await this.handleAtomic(atomicRequest));
974
+ }
975
+ response.body.responses = batchResponses;
976
+ } catch (error) {
977
+ response.body = void 0;
978
+ if (error instanceof ResponseException) {
979
+ response.status = error.status;
980
+ response.error = error.message;
981
+ response.stack = error.stack;
982
+ } else if (error instanceof Error) {
983
+ response.status = 500;
984
+ response.error = error.message || "Internal Server Error";
985
+ response.stack = error.stack || "No stack trace available";
986
+ } else {
987
+ response.status = 500;
988
+ response.error = "Unknown error occurred";
989
+ response.stack = "No stack trace available";
990
+ }
991
+ } finally {
992
+ const t1 = performance.now();
993
+ const message = `< ${response.status} ${request.method} /${request.path} ${Logger.colors.yellow}${Math.round(t1 - t0)}ms${Logger.colors.initial}`;
994
+ if (response.status < 400) Logger.log(message);
995
+ else if (response.status < 500) Logger.warn(message);
996
+ else Logger.error(message);
997
+ if (response.error !== void 0) {
998
+ Logger.error(response.error);
999
+ if (response.stack !== void 0) {
1000
+ Logger.errorStack(response.stack);
1001
+ }
1002
+ }
1003
+ return response;
1004
+ }
1005
+ }
1006
+ normalizeBatchPayload(body) {
1007
+ if (body === null || typeof body !== "object") {
1008
+ throw new BadRequestException("Batch payload must be an object containing a requests array.");
1009
+ }
1010
+ const possiblePayload = body;
1011
+ const { requests } = possiblePayload;
1012
+ if (!Array.isArray(requests)) {
1013
+ throw new BadRequestException("Batch payload must define a requests array.");
1014
+ }
1015
+ const normalizedRequests = requests.map((entry, index) => this.normalizeBatchItem(entry, index));
1016
+ return {
1017
+ requests: normalizedRequests
1018
+ };
1019
+ }
1020
+ normalizeBatchItem(entry, index) {
1021
+ if (entry === null || typeof entry !== "object") {
1022
+ throw new BadRequestException(`Batch request at index ${index} must be an object.`);
1023
+ }
1024
+ const { requestId, path, method, body } = entry;
1025
+ if (requestId !== void 0 && typeof requestId !== "string") {
1026
+ throw new BadRequestException(`Batch request at index ${index} has an invalid requestId.`);
1027
+ }
1028
+ if (typeof path !== "string" || path.length === 0) {
1029
+ throw new BadRequestException(`Batch request at index ${index} must define a non-empty path.`);
1030
+ }
1031
+ if (typeof method !== "string") {
1032
+ throw new BadRequestException(`Batch request at index ${index} must define an HTTP method.`);
1033
+ }
1034
+ const normalizedMethod = method.toUpperCase();
1035
+ if (!isAtomicHttpMethod(normalizedMethod)) {
1036
+ throw new BadRequestException(`Batch request at index ${index} uses the unsupported method ${method}.`);
1037
+ }
1038
+ return {
1039
+ requestId,
1040
+ path,
1041
+ method: normalizedMethod,
1042
+ body
1043
+ };
1044
+ }
901
1045
  /**
902
1046
  * Finds the route definition for a given request.
903
1047
  * This method searches the routing tree for a matching route based on the request's path and method.
@@ -1035,46 +1179,106 @@ Router = _ts_decorate([
1035
1179
  // src/app.ts
1036
1180
  import { app, BrowserWindow, ipcMain, MessageChannelMain } from "electron/main";
1037
1181
 
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, "");
1182
+ // src/socket.ts
1183
+ function _ts_decorate2(decorators, target, key, desc) {
1184
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1185
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1186
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1187
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1188
+ }
1189
+ __name(_ts_decorate2, "_ts_decorate");
1190
+ var _NoxSocket = class _NoxSocket {
1191
+ constructor() {
1192
+ __publicField(this, "messagePorts", /* @__PURE__ */ new Map());
1193
+ }
1194
+ register(senderId, channel) {
1195
+ this.messagePorts.set(senderId, channel);
1196
+ }
1197
+ get(senderId) {
1198
+ return this.messagePorts.get(senderId);
1199
+ }
1200
+ unregister(senderId) {
1201
+ this.messagePorts.delete(senderId);
1202
+ }
1203
+ getSenderIds() {
1204
+ return [
1205
+ ...this.messagePorts.keys()
1206
+ ];
1207
+ }
1208
+ emit(eventName, payload, targetSenderIds) {
1209
+ const normalizedEvent = eventName.trim();
1210
+ if (normalizedEvent.length === 0) {
1211
+ throw new Error("Renderer event name must be a non-empty string.");
1212
+ }
1213
+ const recipients = targetSenderIds ?? this.getSenderIds();
1214
+ let delivered = 0;
1215
+ for (const senderId of recipients) {
1216
+ const channel = this.messagePorts.get(senderId);
1217
+ if (!channel) {
1218
+ Logger.warn(`No message channel found for sender ID: ${senderId} while emitting "${normalizedEvent}".`);
1219
+ continue;
1220
+ }
1221
+ try {
1222
+ channel.port1.postMessage(createRendererEventMessage(normalizedEvent, payload));
1223
+ delivered++;
1224
+ } catch (error) {
1225
+ Logger.error(`[Noxus] Failed to emit "${normalizedEvent}" to sender ${senderId}.`, error);
1226
+ }
1227
+ }
1228
+ return delivered;
1229
+ }
1230
+ emitToRenderer(senderId, eventName, payload) {
1231
+ return this.emit(eventName, payload, [
1232
+ senderId
1233
+ ]) > 0;
1055
1234
  }
1056
1235
  };
1057
- __name(_Request, "Request");
1058
- var Request = _Request;
1236
+ __name(_NoxSocket, "NoxSocket");
1237
+ var NoxSocket = _NoxSocket;
1238
+ NoxSocket = _ts_decorate2([
1239
+ Injectable("singleton")
1240
+ ], NoxSocket);
1059
1241
 
1060
1242
  // src/app.ts
1061
- function _ts_decorate2(decorators, target, key, desc) {
1243
+ function _ts_decorate3(decorators, target, key, desc) {
1062
1244
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1063
1245
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1064
1246
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1065
1247
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1066
1248
  }
1067
- __name(_ts_decorate2, "_ts_decorate");
1249
+ __name(_ts_decorate3, "_ts_decorate");
1068
1250
  function _ts_metadata(k, v) {
1069
1251
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1070
1252
  }
1071
1253
  __name(_ts_metadata, "_ts_metadata");
1072
1254
  var _NoxApp = class _NoxApp {
1073
- constructor(router) {
1255
+ constructor(router, socket) {
1074
1256
  __publicField(this, "router");
1075
- __publicField(this, "messagePorts", /* @__PURE__ */ new Map());
1257
+ __publicField(this, "socket");
1076
1258
  __publicField(this, "app");
1259
+ __publicField(this, "onRendererMessage", /* @__PURE__ */ __name(async (event) => {
1260
+ const { senderId, requestId, path, method, body } = event.data;
1261
+ const channel = this.socket.get(senderId);
1262
+ if (!channel) {
1263
+ Logger.error(`No message channel found for sender ID: ${senderId}`);
1264
+ return;
1265
+ }
1266
+ try {
1267
+ const request = new Request(event, requestId, method, path, body);
1268
+ const response = await this.router.handle(request);
1269
+ channel.port1.postMessage(response);
1270
+ } catch (err) {
1271
+ const response = {
1272
+ requestId,
1273
+ status: 500,
1274
+ body: null,
1275
+ error: err.message || "Internal Server Error"
1276
+ };
1277
+ channel.port1.postMessage(response);
1278
+ }
1279
+ }, "onRendererMessage"));
1077
1280
  this.router = router;
1281
+ this.socket = socket;
1078
1282
  }
1079
1283
  /**
1080
1284
  * Initializes the NoxApp instance.
@@ -1096,44 +1300,19 @@ var _NoxApp = class _NoxApp {
1096
1300
  */
1097
1301
  giveTheRendererAPort(event) {
1098
1302
  const senderId = event.sender.id;
1099
- if (this.messagePorts.has(senderId)) {
1303
+ if (this.socket.get(senderId)) {
1100
1304
  this.shutdownChannel(senderId);
1101
1305
  }
1102
1306
  const channel = new MessageChannelMain();
1103
- this.messagePorts.set(senderId, channel);
1104
- channel.port1.on("message", this.onRendererMessage.bind(this));
1307
+ channel.port1.on("message", this.onRendererMessage);
1105
1308
  channel.port1.start();
1309
+ this.socket.register(senderId, channel);
1106
1310
  event.sender.postMessage("port", {
1107
1311
  senderId
1108
1312
  }, [
1109
1313
  channel.port2
1110
1314
  ]);
1111
1315
  }
1112
- /**
1113
- * Electron specific message handling.
1114
- * Replaces HTTP calls by using Electron's IPC mechanism.
1115
- */
1116
- async onRendererMessage(event) {
1117
- const { senderId, requestId, path, method, body } = event.data;
1118
- const channel = this.messagePorts.get(senderId);
1119
- if (!channel) {
1120
- Logger.error(`No message channel found for sender ID: ${senderId}`);
1121
- return;
1122
- }
1123
- try {
1124
- const request = new Request(event, requestId, method, path, body);
1125
- const response = await this.router.handle(request);
1126
- channel.port1.postMessage(response);
1127
- } catch (err) {
1128
- const response = {
1129
- requestId,
1130
- status: 500,
1131
- body: null,
1132
- error: err.message || "Internal Server Error"
1133
- };
1134
- channel.port1.postMessage(response);
1135
- }
1136
- }
1137
1316
  /**
1138
1317
  * MacOS specific behavior.
1139
1318
  */
@@ -1150,25 +1329,24 @@ var _NoxApp = class _NoxApp {
1150
1329
  * @param remove - Whether to remove the channel from the messagePorts map.
1151
1330
  */
1152
1331
  shutdownChannel(channelSenderId) {
1153
- const channel = this.messagePorts.get(channelSenderId);
1332
+ const channel = this.socket.get(channelSenderId);
1154
1333
  if (!channel) {
1155
1334
  Logger.warn(`No message channel found for sender ID: ${channelSenderId}`);
1156
1335
  return;
1157
1336
  }
1158
- channel.port1.off("message", this.onRendererMessage.bind(this));
1337
+ channel.port1.off("message", this.onRendererMessage);
1159
1338
  channel.port1.close();
1160
1339
  channel.port2.close();
1161
- this.messagePorts.delete(channelSenderId);
1340
+ this.socket.unregister(channelSenderId);
1162
1341
  }
1163
1342
  /**
1164
1343
  * Handles the application shutdown process.
1165
1344
  * This method is called when all windows are closed, and it cleans up the message channels
1166
1345
  */
1167
1346
  async onAllWindowsClosed() {
1168
- this.messagePorts.forEach((channel, senderId) => {
1347
+ for (const senderId of this.socket.getSenderIds()) {
1169
1348
  this.shutdownChannel(senderId);
1170
- });
1171
- this.messagePorts.clear();
1349
+ }
1172
1350
  Logger.info("All windows closed, shutting down application...");
1173
1351
  await this.app?.dispose();
1174
1352
  if (process.platform !== "darwin") {
@@ -1207,11 +1385,12 @@ var _NoxApp = class _NoxApp {
1207
1385
  };
1208
1386
  __name(_NoxApp, "NoxApp");
1209
1387
  var NoxApp = _NoxApp;
1210
- NoxApp = _ts_decorate2([
1388
+ NoxApp = _ts_decorate3([
1211
1389
  Injectable("singleton"),
1212
1390
  _ts_metadata("design:type", Function),
1213
1391
  _ts_metadata("design:paramtypes", [
1214
- typeof Router === "undefined" ? Object : Router
1392
+ typeof Router === "undefined" ? Object : Router,
1393
+ typeof NoxSocket === "undefined" ? Object : NoxSocket
1215
1394
  ])
1216
1395
  ], NoxApp);
1217
1396
 
@@ -1227,6 +1406,86 @@ async function bootstrapApplication(rootModule) {
1227
1406
  return noxApp;
1228
1407
  }
1229
1408
  __name(bootstrapApplication, "bootstrapApplication");
1409
+
1410
+ // src/renderer-events.ts
1411
+ var _RendererEventRegistry = class _RendererEventRegistry {
1412
+ constructor() {
1413
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
1414
+ }
1415
+ /**
1416
+ *
1417
+ */
1418
+ subscribe(eventName, handler) {
1419
+ const normalizedEventName = eventName.trim();
1420
+ if (normalizedEventName.length === 0) {
1421
+ throw new Error("Renderer event name must be a non-empty string.");
1422
+ }
1423
+ const handlers = this.listeners.get(normalizedEventName) ?? /* @__PURE__ */ new Set();
1424
+ handlers.add(handler);
1425
+ this.listeners.set(normalizedEventName, handlers);
1426
+ return {
1427
+ unsubscribe: /* @__PURE__ */ __name(() => this.unsubscribe(normalizedEventName, handler), "unsubscribe")
1428
+ };
1429
+ }
1430
+ /**
1431
+ *
1432
+ */
1433
+ unsubscribe(eventName, handler) {
1434
+ const handlers = this.listeners.get(eventName);
1435
+ if (!handlers) {
1436
+ return;
1437
+ }
1438
+ handlers.delete(handler);
1439
+ if (handlers.size === 0) {
1440
+ this.listeners.delete(eventName);
1441
+ }
1442
+ }
1443
+ /**
1444
+ *
1445
+ */
1446
+ clear(eventName) {
1447
+ if (eventName) {
1448
+ this.listeners.delete(eventName);
1449
+ return;
1450
+ }
1451
+ this.listeners.clear();
1452
+ }
1453
+ /**
1454
+ *
1455
+ */
1456
+ dispatch(message) {
1457
+ const handlers = this.listeners.get(message.event);
1458
+ if (!handlers || handlers.size === 0) {
1459
+ return;
1460
+ }
1461
+ handlers.forEach((handler) => {
1462
+ try {
1463
+ handler(message.payload);
1464
+ } catch (error) {
1465
+ console.error(`[Noxus] Renderer event handler for "${message.event}" threw an error.`, error);
1466
+ }
1467
+ });
1468
+ }
1469
+ /**
1470
+ *
1471
+ */
1472
+ tryDispatchFromMessageEvent(event) {
1473
+ if (!isRendererEventMessage(event.data)) {
1474
+ return false;
1475
+ }
1476
+ this.dispatch(event.data);
1477
+ return true;
1478
+ }
1479
+ /**
1480
+ *
1481
+ */
1482
+ hasHandlers(eventName) {
1483
+ const handlers = this.listeners.get(eventName);
1484
+ return !!handlers && handlers.size > 0;
1485
+ }
1486
+ };
1487
+ __name(_RendererEventRegistry, "RendererEventRegistry");
1488
+ var RendererEventRegistry = _RendererEventRegistry;
1230
1489
  export {
1231
1490
  AppInjector,
1232
1491
  Authorize,
@@ -1256,11 +1515,14 @@ export {
1256
1515
  NotFoundException,
1257
1516
  NotImplementedException,
1258
1517
  NoxApp,
1518
+ NoxSocket,
1259
1519
  Patch,
1260
1520
  PaymentRequiredException,
1261
1521
  Post,
1262
1522
  Put,
1523
+ RENDERER_EVENT_TYPE,
1263
1524
  ROUTE_METADATA_KEY,
1525
+ RendererEventRegistry,
1264
1526
  Request,
1265
1527
  RequestTimeoutException,
1266
1528
  ResponseException,
@@ -1273,6 +1535,7 @@ export {
1273
1535
  UseMiddlewares,
1274
1536
  VariantAlsoNegotiatesException,
1275
1537
  bootstrapApplication,
1538
+ createRendererEventMessage,
1276
1539
  getControllerMetadata,
1277
1540
  getGuardForController,
1278
1541
  getGuardForControllerAction,
@@ -1281,11 +1544,12 @@ export {
1281
1544
  getMiddlewaresForControllerAction,
1282
1545
  getModuleMetadata,
1283
1546
  getRouteMetadata,
1284
- inject
1547
+ inject,
1548
+ isRendererEventMessage
1285
1549
  };
1286
1550
  /**
1287
1551
  * @copyright 2025 NoxFly
1288
1552
  * @license MIT
1289
1553
  * @author NoxFly
1290
1554
  */
1291
- //# sourceMappingURL=noxus.mjs.map
1555
+ //# sourceMappingURL=main.mjs.map
@@ -0,0 +1 @@
1
+ export { o as IBatchRequestItem, p as IBatchRequestPayload, q as IBatchResponsePayload, s as IRendererEventMessage, n as IRequest, I as IResponse, r as RENDERER_EVENT_TYPE, v as RendererEventHandler, x as RendererEventRegistry, w as RendererEventSubscription, R as Request, t as createRendererEventMessage, u as isRendererEventMessage } from './index-5OkVPHfI.mjs';
@@ -0,0 +1 @@
1
+ export { o as IBatchRequestItem, p as IBatchRequestPayload, q as IBatchResponsePayload, s as IRendererEventMessage, n as IRequest, I as IResponse, r as RENDERER_EVENT_TYPE, v as RendererEventHandler, x as RendererEventRegistry, w as RendererEventSubscription, R as Request, t as createRendererEventMessage, u as isRendererEventMessage } from './index-5OkVPHfI.js';