@scpxl/nodejs-framework 1.0.20 → 1.0.22
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/LICENSE +15 -0
- package/README.md +530 -67
- package/dist/application/base-application.js +1 -1
- package/dist/application/base-application.js.map +2 -2
- package/dist/application/command-application.js.map +2 -2
- package/dist/application/web-application.js +1 -0
- package/dist/application/web-application.js.map +2 -2
- package/dist/command/command.js.map +1 -1
- package/dist/config/schema.js +17 -7
- package/dist/config/schema.js.map +2 -2
- package/dist/event/manager.js.map +2 -2
- package/dist/lifecycle/lifecycle-manager.js.map +2 -2
- package/dist/logger/logger.js +21 -1
- package/dist/logger/logger.js.map +2 -2
- package/dist/queue/index.js.map +1 -1
- package/dist/queue/manager.js +24 -12
- package/dist/queue/manager.js.map +2 -2
- package/dist/queue/processor/base.js.map +2 -2
- package/dist/services/aws/s3.js.map +2 -2
- package/dist/util/helper.js +1 -1
- package/dist/util/helper.js.map +2 -2
- package/dist/util/loader.js.map +2 -2
- package/dist/webserver/controller/base.js.map +2 -2
- package/dist/webserver/define-route.js +22 -0
- package/dist/webserver/define-route.js.map +7 -0
- package/dist/webserver/index.js +2 -0
- package/dist/webserver/index.js.map +2 -2
- package/dist/webserver/webserver.interface.js.map +2 -2
- package/dist/webserver/webserver.js +211 -56
- package/dist/webserver/webserver.js.map +2 -2
- package/dist/websocket/websocket-base.js.map +2 -2
- package/dist/websocket/websocket-client.js.map +1 -1
- package/dist/websocket/websocket-server.js +5 -5
- package/dist/websocket/websocket-server.js.map +2 -2
- package/dist/websocket/websocket.interface.js.map +2 -2
- package/package.json +3 -2
- package/dist/api-requester/api-requester.d.ts +0 -32
- package/dist/api-requester/api-requester.d.ts.map +0 -1
- package/dist/api-requester/index.d.ts +0 -3
- package/dist/api-requester/index.d.ts.map +0 -1
- package/dist/application/base-application.d.ts +0 -106
- package/dist/application/base-application.d.ts.map +0 -1
- package/dist/application/base-application.interface.d.ts +0 -161
- package/dist/application/base-application.interface.d.ts.map +0 -1
- package/dist/application/command-application.d.ts +0 -18
- package/dist/application/command-application.d.ts.map +0 -1
- package/dist/application/command-application.interface.d.ts +0 -26
- package/dist/application/command-application.interface.d.ts.map +0 -1
- package/dist/application/index.d.ts +0 -5
- package/dist/application/index.d.ts.map +0 -1
- package/dist/application/web-application.d.ts +0 -43
- package/dist/application/web-application.d.ts.map +0 -1
- package/dist/application/web-application.interface.d.ts +0 -21
- package/dist/application/web-application.interface.d.ts.map +0 -1
- package/dist/auth/index.d.ts +0 -2
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/jwt.d.ts +0 -25
- package/dist/auth/jwt.d.ts.map +0 -1
- package/dist/cache/index.d.ts +0 -2
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/manager.d.ts +0 -107
- package/dist/cache/manager.d.ts.map +0 -1
- package/dist/cluster/cluster-manager.d.ts +0 -15
- package/dist/cluster/cluster-manager.d.ts.map +0 -1
- package/dist/cluster/cluster-manager.interface.d.ts +0 -23
- package/dist/cluster/cluster-manager.interface.d.ts.map +0 -1
- package/dist/cluster/index.d.ts +0 -2
- package/dist/cluster/index.d.ts.map +0 -1
- package/dist/command/command-manager.d.ts +0 -19
- package/dist/command/command-manager.d.ts.map +0 -1
- package/dist/command/command.d.ts +0 -27
- package/dist/command/command.d.ts.map +0 -1
- package/dist/command/command.interface.d.ts +0 -11
- package/dist/command/command.interface.d.ts.map +0 -1
- package/dist/command/index.d.ts +0 -3
- package/dist/command/index.d.ts.map +0 -1
- package/dist/config/env.d.ts +0 -11
- package/dist/config/env.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/schema.d.ts +0 -408
- package/dist/config/schema.d.ts.map +0 -1
- package/dist/database/dynamic-entity-form-decorators.d.ts +0 -31
- package/dist/database/dynamic-entity-form-decorators.d.ts.map +0 -1
- package/dist/database/dynamic-entity.d.ts +0 -15
- package/dist/database/dynamic-entity.d.ts.map +0 -1
- package/dist/database/index.d.ts +0 -5
- package/dist/database/index.d.ts.map +0 -1
- package/dist/database/instance.d.ts +0 -36
- package/dist/database/instance.d.ts.map +0 -1
- package/dist/database/instance.interface.d.ts +0 -5
- package/dist/database/instance.interface.d.ts.map +0 -1
- package/dist/database/manager.d.ts +0 -27
- package/dist/database/manager.d.ts.map +0 -1
- package/dist/database/manager.interface.d.ts +0 -18
- package/dist/database/manager.interface.d.ts.map +0 -1
- package/dist/error/error-reporter.d.ts +0 -96
- package/dist/error/error-reporter.d.ts.map +0 -1
- package/dist/error/error.interface.d.ts +0 -126
- package/dist/error/error.interface.d.ts.map +0 -1
- package/dist/error/framework-errors.d.ts +0 -113
- package/dist/error/framework-errors.d.ts.map +0 -1
- package/dist/error/index.d.ts +0 -6
- package/dist/error/index.d.ts.map +0 -1
- package/dist/event/controller/base.d.ts +0 -23
- package/dist/event/controller/base.d.ts.map +0 -1
- package/dist/event/controller/base.interface.d.ts +0 -11
- package/dist/event/controller/base.interface.d.ts.map +0 -1
- package/dist/event/index.d.ts +0 -5
- package/dist/event/index.d.ts.map +0 -1
- package/dist/event/manager.d.ts +0 -21
- package/dist/event/manager.d.ts.map +0 -1
- package/dist/event/manager.interface.d.ts +0 -134
- package/dist/event/manager.interface.d.ts.map +0 -1
- package/dist/index.d.ts +0 -22
- package/dist/index.d.ts.map +0 -1
- package/dist/lifecycle/exit.d.ts +0 -11
- package/dist/lifecycle/exit.d.ts.map +0 -1
- package/dist/lifecycle/index.d.ts +0 -7
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/lifecycle-manager.d.ts +0 -66
- package/dist/lifecycle/lifecycle-manager.d.ts.map +0 -1
- package/dist/lifecycle/shutdown-controller.d.ts +0 -15
- package/dist/lifecycle/shutdown-controller.d.ts.map +0 -1
- package/dist/lifecycle/types.d.ts +0 -28
- package/dist/lifecycle/types.d.ts.map +0 -1
- package/dist/logger/index.d.ts +0 -2
- package/dist/logger/index.d.ts.map +0 -1
- package/dist/logger/logger.d.ts +0 -55
- package/dist/logger/logger.d.ts.map +0 -1
- package/dist/logger/logger.interface.d.ts +0 -2
- package/dist/logger/logger.interface.d.ts.map +0 -1
- package/dist/performance/cache-performance.d.ts +0 -64
- package/dist/performance/cache-performance.d.ts.map +0 -1
- package/dist/performance/database-performance.d.ts +0 -40
- package/dist/performance/database-performance.d.ts.map +0 -1
- package/dist/performance/index.d.ts +0 -8
- package/dist/performance/index.d.ts.map +0 -1
- package/dist/performance/performance-monitor.d.ts +0 -68
- package/dist/performance/performance-monitor.d.ts.map +0 -1
- package/dist/performance/performance-monitor.plugin.d.ts +0 -24
- package/dist/performance/performance-monitor.plugin.d.ts.map +0 -1
- package/dist/performance/queue-performance.d.ts +0 -46
- package/dist/performance/queue-performance.d.ts.map +0 -1
- package/dist/performance/webserver-performance.d.ts +0 -69
- package/dist/performance/webserver-performance.d.ts.map +0 -1
- package/dist/performance/websocket-performance.d.ts +0 -44
- package/dist/performance/websocket-performance.d.ts.map +0 -1
- package/dist/queue/index.d.ts +0 -6
- package/dist/queue/index.d.ts.map +0 -1
- package/dist/queue/index.interface.d.ts +0 -10
- package/dist/queue/index.interface.d.ts.map +0 -1
- package/dist/queue/job.interface.d.ts +0 -42
- package/dist/queue/job.interface.d.ts.map +0 -1
- package/dist/queue/manager.d.ts +0 -36
- package/dist/queue/manager.d.ts.map +0 -1
- package/dist/queue/manager.interface.d.ts +0 -18
- package/dist/queue/manager.interface.d.ts.map +0 -1
- package/dist/queue/processor/base.d.ts +0 -28
- package/dist/queue/processor/base.d.ts.map +0 -1
- package/dist/queue/processor/processor.interface.d.ts +0 -15
- package/dist/queue/processor/processor.interface.d.ts.map +0 -1
- package/dist/queue/worker.d.ts +0 -14
- package/dist/queue/worker.d.ts.map +0 -1
- package/dist/queue/worker.interface.d.ts +0 -13
- package/dist/queue/worker.interface.d.ts.map +0 -1
- package/dist/redis/index.d.ts +0 -3
- package/dist/redis/index.d.ts.map +0 -1
- package/dist/redis/instance.d.ts +0 -32
- package/dist/redis/instance.d.ts.map +0 -1
- package/dist/redis/instance.interface.d.ts +0 -9
- package/dist/redis/instance.interface.d.ts.map +0 -1
- package/dist/redis/manager.d.ts +0 -15
- package/dist/redis/manager.d.ts.map +0 -1
- package/dist/redis/manager.interface.d.ts +0 -8
- package/dist/redis/manager.interface.d.ts.map +0 -1
- package/dist/request-context/index.d.ts +0 -3
- package/dist/request-context/index.d.ts.map +0 -1
- package/dist/request-context/request-context.d.ts +0 -108
- package/dist/request-context/request-context.d.ts.map +0 -1
- package/dist/request-context/request-context.interface.d.ts +0 -46
- package/dist/request-context/request-context.interface.d.ts.map +0 -1
- package/dist/services/aws/index.d.ts +0 -2
- package/dist/services/aws/index.d.ts.map +0 -1
- package/dist/services/aws/s3.d.ts +0 -54
- package/dist/services/aws/s3.d.ts.map +0 -1
- package/dist/services/aws/s3.interface.d.ts +0 -14
- package/dist/services/aws/s3.interface.d.ts.map +0 -1
- package/dist/services/index.d.ts +0 -2
- package/dist/services/index.d.ts.map +0 -1
- package/dist/util/file.d.ts +0 -58
- package/dist/util/file.d.ts.map +0 -1
- package/dist/util/helper.d.ts +0 -52
- package/dist/util/helper.d.ts.map +0 -1
- package/dist/util/image.d.ts +0 -12
- package/dist/util/image.d.ts.map +0 -1
- package/dist/util/index.d.ts +0 -11
- package/dist/util/index.d.ts.map +0 -1
- package/dist/util/loader.d.ts +0 -19
- package/dist/util/loader.d.ts.map +0 -1
- package/dist/util/num.d.ts +0 -13
- package/dist/util/num.d.ts.map +0 -1
- package/dist/util/os.d.ts +0 -6
- package/dist/util/os.d.ts.map +0 -1
- package/dist/util/str.d.ts +0 -39
- package/dist/util/str.d.ts.map +0 -1
- package/dist/util/time.d.ts +0 -19
- package/dist/util/time.d.ts.map +0 -1
- package/dist/util/time.interface.d.ts +0 -12
- package/dist/util/time.interface.d.ts.map +0 -1
- package/dist/util/timing.d.ts +0 -36
- package/dist/util/timing.d.ts.map +0 -1
- package/dist/util/timing.interface.d.ts +0 -47
- package/dist/util/timing.interface.d.ts.map +0 -1
- package/dist/util/url.d.ts +0 -7
- package/dist/util/url.d.ts.map +0 -1
- package/dist/webserver/controller/auth-middleware.d.ts +0 -21
- package/dist/webserver/controller/auth-middleware.d.ts.map +0 -1
- package/dist/webserver/controller/base.d.ts +0 -41
- package/dist/webserver/controller/base.d.ts.map +0 -1
- package/dist/webserver/controller/base.interface.d.ts +0 -47
- package/dist/webserver/controller/base.interface.d.ts.map +0 -1
- package/dist/webserver/controller/entity.d.ts +0 -94
- package/dist/webserver/controller/entity.d.ts.map +0 -1
- package/dist/webserver/controller/example-auth.d.ts +0 -12
- package/dist/webserver/controller/example-auth.d.ts.map +0 -1
- package/dist/webserver/controller/health.d.ts +0 -15
- package/dist/webserver/controller/health.d.ts.map +0 -1
- package/dist/webserver/index.d.ts +0 -12
- package/dist/webserver/index.d.ts.map +0 -1
- package/dist/webserver/util.d.ts +0 -10
- package/dist/webserver/util.d.ts.map +0 -1
- package/dist/webserver/webserver.d.ts +0 -79
- package/dist/webserver/webserver.d.ts.map +0 -1
- package/dist/webserver/webserver.interface.d.ts +0 -155
- package/dist/webserver/webserver.interface.d.ts.map +0 -1
- package/dist/websocket/controller/client/base.d.ts +0 -12
- package/dist/websocket/controller/client/base.d.ts.map +0 -1
- package/dist/websocket/controller/client/base.interface.d.ts +0 -12
- package/dist/websocket/controller/client/base.interface.d.ts.map +0 -1
- package/dist/websocket/controller/server/base.d.ts +0 -13
- package/dist/websocket/controller/server/base.d.ts.map +0 -1
- package/dist/websocket/controller/server/base.interface.d.ts +0 -13
- package/dist/websocket/controller/server/base.interface.d.ts.map +0 -1
- package/dist/websocket/controllers/client/system.d.ts +0 -6
- package/dist/websocket/controllers/client/system.d.ts.map +0 -1
- package/dist/websocket/controllers/server/system.d.ts +0 -7
- package/dist/websocket/controllers/server/system.d.ts.map +0 -1
- package/dist/websocket/index.d.ts +0 -7
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/routes/client/system.d.ts +0 -3
- package/dist/websocket/routes/client/system.d.ts.map +0 -1
- package/dist/websocket/routes/server/system.d.ts +0 -3
- package/dist/websocket/routes/server/system.d.ts.map +0 -1
- package/dist/websocket/utils.d.ts +0 -9
- package/dist/websocket/utils.d.ts.map +0 -1
- package/dist/websocket/websocket-base.d.ts +0 -15
- package/dist/websocket/websocket-base.d.ts.map +0 -1
- package/dist/websocket/websocket-client-manager.d.ts +0 -53
- package/dist/websocket/websocket-client-manager.d.ts.map +0 -1
- package/dist/websocket/websocket-client-manager.interface.d.ts +0 -8
- package/dist/websocket/websocket-client-manager.interface.d.ts.map +0 -1
- package/dist/websocket/websocket-client.d.ts +0 -35
- package/dist/websocket/websocket-client.d.ts.map +0 -1
- package/dist/websocket/websocket-client.interface.d.ts +0 -14
- package/dist/websocket/websocket-client.interface.d.ts.map +0 -1
- package/dist/websocket/websocket-room-manager.d.ts +0 -32
- package/dist/websocket/websocket-room-manager.d.ts.map +0 -1
- package/dist/websocket/websocket-server.d.ts +0 -92
- package/dist/websocket/websocket-server.d.ts.map +0 -1
- package/dist/websocket/websocket-server.interface.d.ts +0 -16
- package/dist/websocket/websocket-server.interface.d.ts.map +0 -1
- package/dist/websocket/websocket-service.d.ts +0 -44
- package/dist/websocket/websocket-service.d.ts.map +0 -1
- package/dist/websocket/websocket.interface.d.ts +0 -119
- package/dist/websocket/websocket.interface.d.ts.map +0 -1
|
@@ -14,6 +14,7 @@ import { File, Helper, Loader, Time } from "../util/index.js";
|
|
|
14
14
|
import WebServerUtil from "./util.js";
|
|
15
15
|
import { WebServerHealthController } from "../index.js";
|
|
16
16
|
import { enterRequestContext } from "../request-context/index.js";
|
|
17
|
+
import { toJSONSchema } from "zod";
|
|
17
18
|
class WebServer {
|
|
18
19
|
static {
|
|
19
20
|
__name(this, "WebServer");
|
|
@@ -53,7 +54,7 @@ class WebServer {
|
|
|
53
54
|
const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);
|
|
54
55
|
this.applicationConfig = params.applicationConfig;
|
|
55
56
|
this.options = mergedOptions;
|
|
56
|
-
this.routes = params.routes;
|
|
57
|
+
this.routes = [...params.routes ?? []];
|
|
57
58
|
this.redisInstance = params.redisInstance;
|
|
58
59
|
this.queueManager = params.queueManager;
|
|
59
60
|
this.eventManager = params.eventManager;
|
|
@@ -223,20 +224,24 @@ class WebServer {
|
|
|
223
224
|
* Configure routes.
|
|
224
225
|
*/
|
|
225
226
|
async configureRoutes() {
|
|
226
|
-
|
|
227
|
+
await this.loadRoutesFromDirectory();
|
|
228
|
+
const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory ?? "");
|
|
227
229
|
if (!controllersDirectoryExists) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
const routesRequiringControllers = this.routes.length === 0 || this.routes.some((route) => !route.handler);
|
|
231
|
+
if (routesRequiringControllers) {
|
|
232
|
+
Logger.warn({
|
|
233
|
+
message: "Web server controllers directory not found",
|
|
234
|
+
meta: {
|
|
235
|
+
Directory: this.options.controllersDirectory
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
235
240
|
}
|
|
236
|
-
const controllers = await Loader.loadModulesInDirectory({
|
|
241
|
+
const controllers = controllersDirectoryExists ? await Loader.loadModulesInDirectory({
|
|
237
242
|
directory: this.options.controllersDirectory,
|
|
238
243
|
extensions: [".ts", ".js"]
|
|
239
|
-
});
|
|
244
|
+
}) : {};
|
|
240
245
|
this.routes.push(
|
|
241
246
|
{
|
|
242
247
|
type: WebServerRouteType.Default,
|
|
@@ -256,6 +261,22 @@ class WebServer {
|
|
|
256
261
|
for (const route of this.routes) {
|
|
257
262
|
let ControllerClass;
|
|
258
263
|
let controllerName;
|
|
264
|
+
if (route.handler && !route.controller && !route.controllerName) {
|
|
265
|
+
if (route.type && route.type !== WebServerRouteType.Default) {
|
|
266
|
+
throw new Error("Handler-only routes are only supported for default route type");
|
|
267
|
+
}
|
|
268
|
+
if (!("method" in route)) {
|
|
269
|
+
throw new Error("Handler-only routes require an HTTP method");
|
|
270
|
+
}
|
|
271
|
+
const schema = this.buildFastifySchema(route.schema) ?? this.buildLegacySchema(route.validation);
|
|
272
|
+
this.fastifyServer.route({
|
|
273
|
+
method: route.method,
|
|
274
|
+
url: route.path,
|
|
275
|
+
handler: route.handler,
|
|
276
|
+
...schema ? { schema } : {}
|
|
277
|
+
});
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
259
280
|
if (route.controller) {
|
|
260
281
|
ControllerClass = route.controller;
|
|
261
282
|
controllerName = ControllerClass.name;
|
|
@@ -300,7 +321,9 @@ class WebServer {
|
|
|
300
321
|
routeMethod,
|
|
301
322
|
routePath,
|
|
302
323
|
routeAction,
|
|
303
|
-
|
|
324
|
+
routeSchema: route.schema,
|
|
325
|
+
handlerOverride: route.handler?.bind(controllerInstance),
|
|
326
|
+
legacyValidation: route.validation
|
|
304
327
|
});
|
|
305
328
|
break;
|
|
306
329
|
}
|
|
@@ -314,10 +337,7 @@ class WebServer {
|
|
|
314
337
|
const formattedEntityValidationSchema = entityValidationSchema ? {
|
|
315
338
|
type: "object",
|
|
316
339
|
properties: Object.fromEntries(
|
|
317
|
-
Object.entries(entityValidationSchema.keys).map(([key, value]) => [
|
|
318
|
-
key,
|
|
319
|
-
{ type: value.type }
|
|
320
|
-
])
|
|
340
|
+
Object.entries(entityValidationSchema.keys).map(([key, value]) => [key, { type: value.type }])
|
|
321
341
|
),
|
|
322
342
|
required: Object.keys(entityValidationSchema.keys).filter(
|
|
323
343
|
// Dynamic schema inspection of joi describe output; keys are from trusted entity definitions
|
|
@@ -336,7 +356,9 @@ class WebServer {
|
|
|
336
356
|
routeMethod: entityRouteDefinition.method,
|
|
337
357
|
routePath: entityRouteDefinition.path,
|
|
338
358
|
routeAction: entityRouteDefinition.action,
|
|
339
|
-
|
|
359
|
+
routeSchema: route.schema,
|
|
360
|
+
handlerOverride: route.handler?.bind(controllerInstance),
|
|
361
|
+
legacyValidation: entityRouteDefinition.validationSchema
|
|
340
362
|
});
|
|
341
363
|
}
|
|
342
364
|
}
|
|
@@ -349,57 +371,138 @@ class WebServer {
|
|
|
349
371
|
console.log(this.fastifyServer.printRoutes());
|
|
350
372
|
}
|
|
351
373
|
}
|
|
374
|
+
async loadRoutesFromDirectory() {
|
|
375
|
+
const { routesDirectory } = this.options;
|
|
376
|
+
if (!routesDirectory) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const directoryExists = await File.pathExists(routesDirectory);
|
|
380
|
+
if (!directoryExists) {
|
|
381
|
+
this.logger.warn({
|
|
382
|
+
message: "Web server routes directory not found",
|
|
383
|
+
meta: {
|
|
384
|
+
Directory: routesDirectory
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const routeModules = await Loader.loadModulesInDirectory({
|
|
390
|
+
directory: routesDirectory,
|
|
391
|
+
extensions: [".ts", ".js"]
|
|
392
|
+
});
|
|
393
|
+
const loadedRoutes = [];
|
|
394
|
+
for (const [moduleName, exportedRoutes] of Object.entries(routeModules)) {
|
|
395
|
+
const normalizedRoutes = this.normalizeRouteExport(exportedRoutes, moduleName);
|
|
396
|
+
if (normalizedRoutes.length === 0) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
loadedRoutes.push(...normalizedRoutes);
|
|
400
|
+
}
|
|
401
|
+
if (loadedRoutes.length > 0) {
|
|
402
|
+
this.routes.push(...loadedRoutes);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
normalizeRouteExport(exportedValue, moduleName) {
|
|
406
|
+
const ensureRouteArray = /* @__PURE__ */ __name((value) => {
|
|
407
|
+
if (Array.isArray(value)) {
|
|
408
|
+
return value;
|
|
409
|
+
}
|
|
410
|
+
if (value && typeof value === "object") {
|
|
411
|
+
const maybeRoute = value;
|
|
412
|
+
if (Array.isArray(maybeRoute.routes)) {
|
|
413
|
+
return maybeRoute.routes;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return value ? [value] : [];
|
|
417
|
+
}, "ensureRouteArray");
|
|
418
|
+
const routeCandidates = ensureRouteArray(exportedValue);
|
|
419
|
+
const validRoutes = [];
|
|
420
|
+
for (const [index, candidate] of routeCandidates.entries()) {
|
|
421
|
+
if (this.isValidRoute(candidate)) {
|
|
422
|
+
validRoutes.push(candidate);
|
|
423
|
+
} else {
|
|
424
|
+
this.logger.warn({
|
|
425
|
+
message: "Invalid web server route definition skipped",
|
|
426
|
+
meta: {
|
|
427
|
+
Module: moduleName,
|
|
428
|
+
Index: index
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (validRoutes.length === 0 && routeCandidates.length > 0) {
|
|
434
|
+
this.logger.warn({
|
|
435
|
+
message: "No valid routes exported from module",
|
|
436
|
+
meta: {
|
|
437
|
+
Module: moduleName
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
return validRoutes;
|
|
442
|
+
}
|
|
443
|
+
isValidRoute(route) {
|
|
444
|
+
if (!route || typeof route !== "object") {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
const candidate = route;
|
|
448
|
+
const routePath = candidate.path;
|
|
449
|
+
if (typeof routePath !== "string" || routePath.length === 0) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
const routeType = candidate.type ?? WebServerRouteType.Default;
|
|
453
|
+
const controllerProvided = typeof candidate.controller === "function" || typeof candidate.controllerName === "string";
|
|
454
|
+
if (routeType === WebServerRouteType.Entity || routeType === "entity") {
|
|
455
|
+
return controllerProvided && typeof candidate.entityName === "string" && candidate.entityName.length > 0;
|
|
456
|
+
}
|
|
457
|
+
if (!controllerProvided) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
const method = candidate.method;
|
|
461
|
+
const action = candidate.action;
|
|
462
|
+
const isValidMethod = typeof method === "string" || Array.isArray(method) && method.length > 0 && method.every((m) => typeof m === "string");
|
|
463
|
+
const isValidAction = typeof action === "string" && action.length > 0;
|
|
464
|
+
return isValidMethod && isValidAction;
|
|
465
|
+
}
|
|
352
466
|
async defineRoute({
|
|
353
467
|
controllerInstance,
|
|
354
468
|
controllerName,
|
|
355
469
|
routeMethod,
|
|
356
470
|
routePath,
|
|
357
471
|
routeAction,
|
|
358
|
-
|
|
472
|
+
routeSchema,
|
|
473
|
+
handlerOverride,
|
|
474
|
+
legacyValidation
|
|
359
475
|
}) {
|
|
360
|
-
|
|
361
|
-
|
|
476
|
+
let handler = handlerOverride;
|
|
477
|
+
if (!handler) {
|
|
478
|
+
if (!routeAction) {
|
|
479
|
+
throw new Error("Route action is required when handler override is not provided");
|
|
480
|
+
}
|
|
481
|
+
if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ["__proto__", "prototype", "constructor"].includes(routeAction)) {
|
|
482
|
+
throw new Error("Invalid controller action name");
|
|
483
|
+
}
|
|
484
|
+
const controllerHandler = controllerInstance[routeAction];
|
|
485
|
+
if (!controllerHandler) {
|
|
486
|
+
Logger.warn({
|
|
487
|
+
message: "Web server controller action not found",
|
|
488
|
+
meta: {
|
|
489
|
+
Controller: controllerName,
|
|
490
|
+
Action: routeAction
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
throw new Error("Web server controller action not found");
|
|
494
|
+
}
|
|
495
|
+
handler = controllerHandler.bind(controllerInstance);
|
|
362
496
|
}
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
365
|
-
|
|
366
|
-
message: "Web server controller action not found",
|
|
367
|
-
meta: {
|
|
368
|
-
Controller: controllerName,
|
|
369
|
-
Action: routeAction
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
throw new Error("Web server controller action not found");
|
|
497
|
+
const fastifySchema = this.buildFastifySchema(routeSchema) ?? this.buildLegacySchema(legacyValidation);
|
|
498
|
+
if (!handler) {
|
|
499
|
+
throw new Error("Route handler could not be resolved");
|
|
373
500
|
}
|
|
374
501
|
this.fastifyServer.route({
|
|
375
502
|
method: routeMethod,
|
|
376
503
|
url: routePath,
|
|
377
|
-
handler
|
|
378
|
-
|
|
379
|
-
if (!routeValidation?.schema) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
const validate = request.compileValidationSchema(routeValidation.schema);
|
|
383
|
-
let dataToValidate;
|
|
384
|
-
switch (routeValidation.type) {
|
|
385
|
-
case "body":
|
|
386
|
-
dataToValidate = request.body;
|
|
387
|
-
break;
|
|
388
|
-
case "query":
|
|
389
|
-
dataToValidate = request.query;
|
|
390
|
-
break;
|
|
391
|
-
case "params":
|
|
392
|
-
dataToValidate = request.params;
|
|
393
|
-
break;
|
|
394
|
-
default:
|
|
395
|
-
dataToValidate = void 0;
|
|
396
|
-
}
|
|
397
|
-
if (!validate(dataToValidate)) {
|
|
398
|
-
return reply.code(400).send({
|
|
399
|
-
error: validate.errors
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}, "preValidation")
|
|
504
|
+
handler,
|
|
505
|
+
...fastifySchema ? { schema: fastifySchema } : {}
|
|
403
506
|
});
|
|
404
507
|
}
|
|
405
508
|
/**
|
|
@@ -424,6 +527,58 @@ class WebServer {
|
|
|
424
527
|
this._isReady = false;
|
|
425
528
|
await this.fastifyServer.close();
|
|
426
529
|
}
|
|
530
|
+
buildFastifySchema(routeSchema) {
|
|
531
|
+
if (!routeSchema) {
|
|
532
|
+
return void 0;
|
|
533
|
+
}
|
|
534
|
+
const schema = {};
|
|
535
|
+
if (routeSchema.params) {
|
|
536
|
+
schema.params = this.convertZodSchema(routeSchema.params);
|
|
537
|
+
}
|
|
538
|
+
if (routeSchema.querystring) {
|
|
539
|
+
schema.querystring = this.convertZodSchema(routeSchema.querystring);
|
|
540
|
+
}
|
|
541
|
+
if (routeSchema.body) {
|
|
542
|
+
schema.body = this.convertZodSchema(routeSchema.body);
|
|
543
|
+
}
|
|
544
|
+
if (routeSchema.headers) {
|
|
545
|
+
schema.headers = this.convertZodSchema(routeSchema.headers);
|
|
546
|
+
}
|
|
547
|
+
if (routeSchema.response) {
|
|
548
|
+
const responses = routeSchema.response;
|
|
549
|
+
const responseEntries = Object.entries(responses).filter(([statusCode]) => /^[1-5][0-9]{2}$/.test(statusCode)).map(([statusCode, responseSchema]) => [statusCode, this.convertZodSchema(responseSchema)]);
|
|
550
|
+
if (responseEntries.length > 0) {
|
|
551
|
+
schema.response = Object.fromEntries(responseEntries);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return schema;
|
|
555
|
+
}
|
|
556
|
+
buildLegacySchema(legacyValidation) {
|
|
557
|
+
if (!legacyValidation) {
|
|
558
|
+
return void 0;
|
|
559
|
+
}
|
|
560
|
+
const schema = {};
|
|
561
|
+
switch (legacyValidation.type) {
|
|
562
|
+
case "body":
|
|
563
|
+
schema.body = legacyValidation.schema;
|
|
564
|
+
break;
|
|
565
|
+
case "query":
|
|
566
|
+
schema.querystring = legacyValidation.schema;
|
|
567
|
+
break;
|
|
568
|
+
case "params":
|
|
569
|
+
schema.params = legacyValidation.schema;
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
return schema;
|
|
573
|
+
}
|
|
574
|
+
convertZodSchema(zodSchema) {
|
|
575
|
+
const jsonSchema = toJSONSchema(zodSchema);
|
|
576
|
+
if (jsonSchema && typeof jsonSchema === "object" && "$schema" in jsonSchema) {
|
|
577
|
+
const { $schema: _jsonSchemaVersion, ...rest } = jsonSchema;
|
|
578
|
+
return rest;
|
|
579
|
+
}
|
|
580
|
+
return jsonSchema;
|
|
581
|
+
}
|
|
427
582
|
/**
|
|
428
583
|
* Check if web server is ready to accept traffic.
|
|
429
584
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/webserver/webserver.ts"],
|
|
4
|
-
"sourcesContent": ["import crypto from 'node:crypto';\nimport Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest, type HTTPMethods } from 'fastify';\nimport cors from '@fastify/cors';\nimport helmet from '@fastify/helmet';\nimport rateLimit from '@fastify/rate-limit';\nimport multipart from '@fastify/multipart';\nimport {\n type WebServerConstructorParams,\n type WebServerOptions,\n type WebServerRoute,\n WebServerRouteType,\n} from './webserver.interface.js';\nimport { Logger } from '../logger/index.js';\nimport { File, Helper, Loader, Time } from '../util/index.js';\nimport WebServerUtil from './util.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport type { DatabaseInstance } from '../database/index.js';\nimport type { WebServerBaseControllerType } from './controller/base.interface.js';\nimport type { QueueManager } from '../queue/index.js';\nimport { WebServerHealthController } from '../index.js';\nimport type { LifecycleManager } from '../lifecycle/lifecycle-manager.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type EventManager from '../event/manager.js';\nimport { enterRequestContext } from '../request-context/index.js';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n startTime?: number;\n requestId?: string;\n }\n}\n\nclass WebServer {\n private logger: typeof Logger = Logger;\n\n private applicationConfig: ApplicationConfig;\n\n private options: WebServerOptions;\n private routes: WebServerRoute[];\n\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private eventManager: EventManager;\n private databaseInstance: DatabaseInstance;\n\n public fastifyServer: FastifyInstance;\n\n private lifecycleManager: LifecycleManager;\n private _isReady = false;\n\n constructor(params: WebServerConstructorParams & { lifecycleManager: LifecycleManager }) {\n // Define default options\n const defaultOptions: Partial<WebServerOptions> = {\n host: '0.0.0.0',\n port: 3001,\n cors: {\n enabled: false,\n },\n errors: {\n verbose: false,\n },\n debug: {\n printRoutes: false,\n simulateSlowConnection: {\n enabled: false,\n delay: 250,\n },\n },\n log: {\n startUp: true,\n },\n };\n\n // Merge default options\n const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);\n\n this.applicationConfig = params.applicationConfig;\n\n this.options = mergedOptions;\n this.routes = params.routes;\n\n this.redisInstance = params.redisInstance;\n this.queueManager = params.queueManager;\n this.eventManager = params.eventManager;\n this.databaseInstance = params.databaseInstance;\n this.lifecycleManager = params.lifecycleManager;\n\n // Create Fastify server\n const defaultBodyLimit = 25 * 1024 * 1024; // 25MB (safer default)\n const defaultConnectionTimeout = 10 * 1000; // 10 seconds (safer default)\n\n this.fastifyServer = Fastify({\n logger: false,\n bodyLimit: this.options.bodyLimit ?? defaultBodyLimit,\n connectionTimeout: this.options.connectionTimeout ?? defaultConnectionTimeout,\n });\n }\n\n /**\n * Load web server.\n */\n public async load(): Promise<void> {\n // Configure security (helmet, rate limiting)\n await this.configureSecurity();\n\n // Configure hooks\n this.configureHooks();\n\n // Configure CORS\n this.configureCORS();\n\n // Configure multipart uploads\n this.configureMultipartUploads();\n\n // Configure routes\n await this.configureRoutes();\n }\n\n /**\n * Configure security features (Helmet, Rate Limiting)\n */\n private async configureSecurity(): Promise<void> {\n const security = this.options.security ?? {};\n\n // Configure Helmet for security headers\n const helmetConfig = security.helmet ?? { enabled: true };\n if (helmetConfig.enabled !== false) {\n await this.fastifyServer.register(helmet, {\n contentSecurityPolicy: helmetConfig.contentSecurityPolicy !== false,\n crossOriginEmbedderPolicy: helmetConfig.crossOriginEmbedderPolicy !== false,\n crossOriginOpenerPolicy: helmetConfig.crossOriginOpenerPolicy !== false,\n crossOriginResourcePolicy: helmetConfig.crossOriginResourcePolicy !== false,\n dnsPrefetchControl: helmetConfig.dnsPrefetchControl !== false,\n frameguard: helmetConfig.frameguard !== false,\n hidePoweredBy: helmetConfig.hidePoweredBy !== false,\n hsts: helmetConfig.hsts !== false,\n ieNoOpen: helmetConfig.ieNoOpen !== false,\n noSniff: helmetConfig.noSniff !== false,\n originAgentCluster: helmetConfig.originAgentCluster !== false,\n permittedCrossDomainPolicies: helmetConfig.permittedCrossDomainPolicies !== false,\n referrerPolicy: helmetConfig.referrerPolicy !== false,\n xssFilter: helmetConfig.xssFilter !== false,\n });\n }\n\n // Configure rate limiting\n const rateLimitConfig = security.rateLimit ?? { enabled: true };\n if (rateLimitConfig.enabled !== false) {\n await this.fastifyServer.register(rateLimit, {\n max: rateLimitConfig.max ?? 1000,\n timeWindow: rateLimitConfig.timeWindow ?? '1 minute',\n ban: rateLimitConfig.ban,\n cache: rateLimitConfig.cache ?? 5000,\n });\n }\n\n // Warn about wildcard CORS in production\n if (process.env.NODE_ENV === 'production' && this.options.cors?.enabled) {\n const corsConfig = this.options.cors as { enabled: true; urls: string[] };\n if (corsConfig.urls?.includes('*')) {\n this.logger.warn({\n message: 'Wildcard CORS (*) is enabled in production - this is a security risk',\n meta: {\n recommendation: 'Specify allowed origins explicitly',\n },\n });\n }\n }\n }\n\n /**\n * Configure hooks.\n */\n private configureHooks(): void {\n this.fastifyServer.addHook('onListen', async () => this.onListen());\n this.fastifyServer.addHook('onRequest', async request => this.onRequest(request));\n this.fastifyServer.addHook('onResponse', async (request, reply) => this.onResponse(request, reply));\n this.fastifyServer.addHook('onError', async (request, reply, error) => this.onError(request, reply, error));\n this.fastifyServer.addHook('onClose', async () => this.onClose());\n\n // if (process.env.NODE_ENV === 'local') {\n // this.fastifyServer.addHook('onSend', (request, reply, payload, done) => {\n // reply.header('Cache-Control', 'no-store');\n // done();\n // });\n // }\n }\n\n private async onListen(): Promise<void> {\n const address = this.fastifyServer.server.address();\n const port = typeof address === 'string' ? address : address?.port;\n\n if (this.options.log?.startUp) {\n this.log('Started', {\n Host: this.options.host,\n Port: port,\n // CORS: this.options.cors?.enabled && this.options.cors?..length > 0 ? this.options.corsUrls.join(', ') : 'Disabled',\n CORS: this.options.cors?.enabled ? this.options.cors.urls.join(', ') : 'Disabled',\n 'Fastify Version': this.fastifyServer.version,\n });\n }\n }\n\n private async onRequest(request: FastifyRequest): Promise<void> {\n if (\n this.options.debug?.simulateSlowConnection?.enabled &&\n this.options.debug?.simulateSlowConnection?.delay &&\n this.options.debug?.simulateSlowConnection?.delay > 0\n ) {\n await new Promise(resolve => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));\n }\n\n // Generate or use existing request ID for correlation\n const requestId = (request.headers['x-request-id'] as string | undefined) ?? crypto.randomUUID();\n request.requestId = requestId;\n\n const pathsToIgnore = ['/health/live', '/health/ready'];\n\n if (pathsToIgnore.includes(request.url) || request.method === 'OPTIONS') {\n // ...\n } else {\n const startTime = Time.now();\n request.startTime = startTime;\n\n // Initialize AsyncLocalStorage context for this request\n // Using enterWith() to set context for the current async execution\n enterRequestContext({ requestId, startTime });\n }\n }\n\n private async onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void> {\n // Add request ID to response headers for client-side correlation\n if (request.requestId) {\n reply.header('X-Request-ID', request.requestId);\n }\n\n if (!request.startTime) {\n return;\n }\n\n const executionTime = Time.calculateElapsedTimeMs({\n startTime: request.startTime,\n });\n const formattedExecutionTime = Time.formatTime({\n time: executionTime,\n numDecimals: 3,\n });\n\n const ip = request.headers['x-forwarded-for'] ?? request.ip;\n\n const logParams: Record<string, unknown> = {\n Method: request.method,\n Path: request.url,\n Status: reply.statusCode,\n };\n\n if (process.env.NODE_ENV !== 'development') {\n logParams.IP = ip.toString();\n }\n\n logParams.Time = formattedExecutionTime;\n\n // if (cluster.isWorker && cluster.worker) {\n // logParams.Worker = cluster.worker.id;\n // }\n\n this.log('API Request', logParams);\n }\n\n private async onError(request: FastifyRequest, reply: FastifyReply, error: Error): Promise<void> {\n // Adjusted for Fastify types\n Logger.error({ error });\n // Implement any additional logic here\n }\n\n private async onClose(): Promise<void> {\n this.log('Stopped');\n }\n\n private configureCORS(): void {\n if (!this.options.cors?.enabled) {\n return;\n }\n\n // Handle wildcard origin for development\n const origin = this.options.cors.urls.includes('*') ? true : this.options.cors.urls;\n\n this.fastifyServer.register(cors, {\n origin,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n // credentials: true,\n });\n }\n\n private configureMultipartUploads(): void {\n this.fastifyServer.register(multipart, {\n // attachFieldsToBody: true,\n limits: {\n fieldNameSize: 100,\n fieldSize: 1024 * 1024 * 10,\n fields: 10,\n fileSize: 1024 * 1024 * 1024 * 10, // 10GB file size limit\n files: 1,\n headerPairs: 2000,\n },\n });\n }\n\n /**\n * Configure routes.\n */\n private async configureRoutes(): Promise<void> {\n // Check if controllers directory exists\n const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory);\n\n if (!controllersDirectoryExists) {\n Logger.warn({\n message: 'Web server controllers directory not found',\n meta: {\n Directory: this.options.controllersDirectory,\n },\n });\n\n return;\n }\n\n // Load controllers\n const controllers = await Loader.loadModulesInDirectory({\n directory: this.options.controllersDirectory,\n extensions: ['.ts', '.js'],\n });\n\n // Add health check routes\n this.routes.push(\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/live',\n controller: WebServerHealthController,\n action: 'live',\n },\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/ready',\n controller: WebServerHealthController,\n action: 'ready',\n },\n );\n\n // Go through each route\n for (const route of this.routes) {\n let ControllerClass: WebServerBaseControllerType;\n\n let controllerName;\n\n if (route.controller) {\n ControllerClass = route.controller;\n\n controllerName = ControllerClass.name;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName];\n\n controllerName = route.controllerName;\n } else {\n throw new Error('Web server controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n const controllerPath = `${this.options.controllersDirectory}/${route.controllerName}.ts`;\n\n Logger.warn({\n message: 'Web server controller not found',\n meta: {\n Controller: route.controllerName,\n Path: controllerPath,\n Route: `${route.path}`,\n },\n });\n\n continue;\n }\n\n // Initialize controller instance\n const controllerInstance = new ControllerClass({\n applicationConfig: this.applicationConfig,\n webServerOptions: this.options,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n eventManager: this.eventManager,\n databaseInstance: this.databaseInstance,\n lifecycleManager: this.lifecycleManager,\n });\n\n let routeMethod;\n let routeAction;\n let routePath;\n\n switch (route.type) {\n case WebServerRouteType.Default: {\n routeMethod = route.method;\n routeAction = route.action;\n routePath = route.path;\n\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeValidation: route.validation,\n });\n\n break;\n }\n case WebServerRouteType.Entity: {\n if (this.applicationConfig.database && this.applicationConfig.database.enabled === true) {\n const entityModel = await Loader.loadEntityModule({\n entitiesDirectory: this.applicationConfig.database.entitiesDirectory,\n entityName: route.entityName,\n });\n\n const entityValidationSchema = entityModel.schema?.describe();\n\n const formattedEntityValidationSchema = entityValidationSchema\n ? {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(entityValidationSchema.keys).map(([key, value]) => [\n key,\n { type: (value as any).type },\n ]),\n ),\n required: Object.keys(entityValidationSchema.keys).filter(\n // Dynamic schema inspection of joi describe output; keys are from trusted entity definitions\n // eslint-disable-next-line security/detect-object-injection\n key => entityValidationSchema.keys[key].flags?.presence === 'required',\n ),\n }\n : {};\n\n const entityRouteDefinitions = WebServerUtil.getEntityRouteDefinitions({\n basePath: route.path,\n entityValidationSchema: formattedEntityValidationSchema,\n });\n\n for (const entityRouteDefinition of entityRouteDefinitions) {\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod: entityRouteDefinition.method,\n routePath: entityRouteDefinition.path,\n routeAction: entityRouteDefinition.action,\n routeValidation: entityRouteDefinition.validationSchema,\n });\n }\n }\n\n break;\n }\n }\n }\n\n if (this.options.debug?.printRoutes) {\n this.log('Routes:');\n\n console.log(this.fastifyServer.printRoutes());\n }\n }\n\n public async defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeValidation,\n }: {\n controllerInstance: any;\n controllerName: string;\n routeMethod: HTTPMethods | HTTPMethods[];\n routePath: string;\n routeAction: string;\n routeValidation?: {\n type: 'body' | 'query' | 'params';\n schema: { [key: string]: any };\n };\n }): Promise<void> {\n // Get controller action handler\n // Validate action name to avoid prototype access\n if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ['__proto__', 'prototype', 'constructor'].includes(routeAction)) {\n throw new Error('Invalid controller action name');\n }\n // Dynamic access guarded by regex + deny list (keys validated above)\n const controllerHandler = controllerInstance[routeAction as keyof typeof controllerInstance];\n\n if (!controllerHandler) {\n Logger.warn({\n message: 'Web server controller action not found',\n meta: {\n Controller: controllerName,\n Action: routeAction,\n },\n });\n\n throw new Error('Web server controller action not found');\n }\n\n // Add route\n this.fastifyServer.route({\n method: routeMethod,\n url: routePath,\n handler: controllerHandler,\n preValidation: async (request, reply) => {\n if (!routeValidation?.schema) {\n // Logger.warn('Web server route validation schema not found', {\n // Controller: controllerName,\n // Action: routeAction,\n // });\n\n return;\n }\n\n const validate = request.compileValidationSchema(routeValidation.schema);\n\n // Avoid dynamic request[...] access for security lint; map explicitly\n let dataToValidate: any;\n switch (routeValidation.type) {\n case 'body':\n dataToValidate = request.body;\n break;\n case 'query':\n dataToValidate = request.query;\n break;\n case 'params':\n dataToValidate = request.params;\n break;\n default:\n dataToValidate = undefined;\n }\n\n if (!validate(dataToValidate)) {\n return reply.code(400).send({\n error: validate.errors,\n });\n }\n },\n });\n }\n\n /**\n * Start web server.\n */\n public async start(): Promise<void> {\n try {\n await this.fastifyServer.listen({\n host: this.options.host,\n port: this.options.port,\n });\n this._isReady = true;\n } catch (error) {\n Logger.error({ error });\n throw error;\n }\n }\n\n /**\n * Stop web server.\n */\n public async stop(): Promise<void> {\n this._isReady = false;\n // Close Fastify server\n await this.fastifyServer.close();\n }\n\n /**\n * Check if web server is ready to accept traffic.\n */\n public isReady(): boolean {\n return this._isReady && this.fastifyServer.server?.listening === true;\n }\n\n /**\n * Log web server message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'webServer', message, meta });\n }\n}\n\nexport default WebServer;\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto';\nimport Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n type FastifySchema,\n type HTTPMethods,\n} from 'fastify';\nimport cors from '@fastify/cors';\nimport helmet from '@fastify/helmet';\nimport rateLimit from '@fastify/rate-limit';\nimport multipart from '@fastify/multipart';\nimport {\n type AnyRouteSchemaDefinition,\n type WebServerConstructorParams,\n type WebServerOptions,\n type WebServerRoute,\n WebServerRouteType,\n} from './webserver.interface.js';\nimport { Logger } from '../logger/index.js';\nimport { File, Helper, Loader, Time } from '../util/index.js';\nimport WebServerUtil from './util.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport type { DatabaseInstance } from '../database/index.js';\nimport type { ControllerAction, WebServerBaseControllerType } from './controller/base.interface.js';\nimport type { QueueManager } from '../queue/index.js';\nimport { WebServerHealthController } from '../index.js';\nimport type { LifecycleManager } from '../lifecycle/lifecycle-manager.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type EventManager from '../event/manager.js';\nimport { enterRequestContext } from '../request-context/index.js';\nimport { toJSONSchema, type z } from 'zod';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n startTime?: number;\n requestId?: string;\n }\n}\n\nclass WebServer {\n private logger: typeof Logger = Logger;\n\n private applicationConfig: ApplicationConfig;\n\n private options: WebServerOptions;\n private routes: WebServerRoute[];\n\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private eventManager: EventManager;\n private databaseInstance: DatabaseInstance;\n\n public fastifyServer: FastifyInstance;\n\n private lifecycleManager: LifecycleManager;\n private _isReady = false;\n\n constructor(params: WebServerConstructorParams & { lifecycleManager: LifecycleManager }) {\n // Define default options\n const defaultOptions: Partial<WebServerOptions> = {\n host: '0.0.0.0',\n port: 3001,\n cors: {\n enabled: false,\n },\n errors: {\n verbose: false,\n },\n debug: {\n printRoutes: false,\n simulateSlowConnection: {\n enabled: false,\n delay: 250,\n },\n },\n log: {\n startUp: true,\n },\n };\n\n // Merge default options\n const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);\n\n this.applicationConfig = params.applicationConfig;\n\n this.options = mergedOptions;\n this.routes = [...(params.routes ?? [])];\n\n this.redisInstance = params.redisInstance;\n this.queueManager = params.queueManager;\n this.eventManager = params.eventManager;\n this.databaseInstance = params.databaseInstance;\n this.lifecycleManager = params.lifecycleManager;\n\n // Create Fastify server\n const defaultBodyLimit = 25 * 1024 * 1024; // 25MB (safer default)\n const defaultConnectionTimeout = 10 * 1000; // 10 seconds (safer default)\n\n this.fastifyServer = Fastify({\n logger: false,\n bodyLimit: this.options.bodyLimit ?? defaultBodyLimit,\n connectionTimeout: this.options.connectionTimeout ?? defaultConnectionTimeout,\n });\n }\n\n /**\n * Load web server.\n */\n public async load(): Promise<void> {\n // Configure security (helmet, rate limiting)\n await this.configureSecurity();\n\n // Configure hooks\n this.configureHooks();\n\n // Configure CORS\n this.configureCORS();\n\n // Configure multipart uploads\n this.configureMultipartUploads();\n\n // Configure routes\n await this.configureRoutes();\n }\n\n /**\n * Configure security features (Helmet, Rate Limiting)\n */\n private async configureSecurity(): Promise<void> {\n const security = this.options.security ?? {};\n\n // Configure Helmet for security headers\n const helmetConfig = security.helmet ?? { enabled: true };\n if (helmetConfig.enabled !== false) {\n await this.fastifyServer.register(helmet, {\n contentSecurityPolicy: helmetConfig.contentSecurityPolicy !== false,\n crossOriginEmbedderPolicy: helmetConfig.crossOriginEmbedderPolicy !== false,\n crossOriginOpenerPolicy: helmetConfig.crossOriginOpenerPolicy !== false,\n crossOriginResourcePolicy: helmetConfig.crossOriginResourcePolicy !== false,\n dnsPrefetchControl: helmetConfig.dnsPrefetchControl !== false,\n frameguard: helmetConfig.frameguard !== false,\n hidePoweredBy: helmetConfig.hidePoweredBy !== false,\n hsts: helmetConfig.hsts !== false,\n ieNoOpen: helmetConfig.ieNoOpen !== false,\n noSniff: helmetConfig.noSniff !== false,\n originAgentCluster: helmetConfig.originAgentCluster !== false,\n permittedCrossDomainPolicies: helmetConfig.permittedCrossDomainPolicies !== false,\n referrerPolicy: helmetConfig.referrerPolicy !== false,\n xssFilter: helmetConfig.xssFilter !== false,\n });\n }\n\n // Configure rate limiting\n const rateLimitConfig = security.rateLimit ?? { enabled: true };\n if (rateLimitConfig.enabled !== false) {\n await this.fastifyServer.register(rateLimit, {\n max: rateLimitConfig.max ?? 1000,\n timeWindow: rateLimitConfig.timeWindow ?? '1 minute',\n ban: rateLimitConfig.ban,\n cache: rateLimitConfig.cache ?? 5000,\n });\n }\n\n // Warn about wildcard CORS in production\n if (process.env.NODE_ENV === 'production' && this.options.cors?.enabled) {\n const corsConfig = this.options.cors as { enabled: true; urls: string[] };\n if (corsConfig.urls?.includes('*')) {\n this.logger.warn({\n message: 'Wildcard CORS (*) is enabled in production - this is a security risk',\n meta: {\n recommendation: 'Specify allowed origins explicitly',\n },\n });\n }\n }\n }\n\n /**\n * Configure hooks.\n */\n private configureHooks(): void {\n this.fastifyServer.addHook('onListen', async () => this.onListen());\n this.fastifyServer.addHook('onRequest', async request => this.onRequest(request));\n this.fastifyServer.addHook('onResponse', async (request, reply) => this.onResponse(request, reply));\n this.fastifyServer.addHook('onError', async (request, reply, error) => this.onError(request, reply, error));\n this.fastifyServer.addHook('onClose', async () => this.onClose());\n\n // if (process.env.NODE_ENV === 'local') {\n // this.fastifyServer.addHook('onSend', (request, reply, payload, done) => {\n // reply.header('Cache-Control', 'no-store');\n // done();\n // });\n // }\n }\n\n private async onListen(): Promise<void> {\n const address = this.fastifyServer.server.address();\n const port = typeof address === 'string' ? address : address?.port;\n\n if (this.options.log?.startUp) {\n this.log('Started', {\n Host: this.options.host,\n Port: port,\n // CORS: this.options.cors?.enabled && this.options.cors?..length > 0 ? this.options.corsUrls.join(', ') : 'Disabled',\n CORS: this.options.cors?.enabled ? this.options.cors.urls.join(', ') : 'Disabled',\n 'Fastify Version': this.fastifyServer.version,\n });\n }\n }\n\n private async onRequest(request: FastifyRequest): Promise<void> {\n if (\n this.options.debug?.simulateSlowConnection?.enabled &&\n this.options.debug?.simulateSlowConnection?.delay &&\n this.options.debug?.simulateSlowConnection?.delay > 0\n ) {\n await new Promise(resolve => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));\n }\n\n // Generate or use existing request ID for correlation\n const requestId = (request.headers['x-request-id'] as string | undefined) ?? crypto.randomUUID();\n request.requestId = requestId;\n\n const pathsToIgnore = ['/health/live', '/health/ready'];\n\n if (pathsToIgnore.includes(request.url) || request.method === 'OPTIONS') {\n // ...\n } else {\n const startTime = Time.now();\n request.startTime = startTime;\n\n // Initialize AsyncLocalStorage context for this request\n // Using enterWith() to set context for the current async execution\n enterRequestContext({ requestId, startTime });\n }\n }\n\n private async onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void> {\n // Add request ID to response headers for client-side correlation\n if (request.requestId) {\n reply.header('X-Request-ID', request.requestId);\n }\n\n if (!request.startTime) {\n return;\n }\n\n const executionTime = Time.calculateElapsedTimeMs({\n startTime: request.startTime,\n });\n const formattedExecutionTime = Time.formatTime({\n time: executionTime,\n numDecimals: 3,\n });\n\n const ip = request.headers['x-forwarded-for'] ?? request.ip;\n\n const logParams: Record<string, unknown> = {\n Method: request.method,\n Path: request.url,\n Status: reply.statusCode,\n };\n\n if (process.env.NODE_ENV !== 'development') {\n logParams.IP = ip.toString();\n }\n\n logParams.Time = formattedExecutionTime;\n\n // if (cluster.isWorker && cluster.worker) {\n // logParams.Worker = cluster.worker.id;\n // }\n\n this.log('API Request', logParams);\n }\n\n private async onError(request: FastifyRequest, reply: FastifyReply, error: Error): Promise<void> {\n // Adjusted for Fastify types\n Logger.error({ error });\n // Implement any additional logic here\n }\n\n private async onClose(): Promise<void> {\n this.log('Stopped');\n }\n\n private configureCORS(): void {\n if (!this.options.cors?.enabled) {\n return;\n }\n\n // Handle wildcard origin for development\n const origin = this.options.cors.urls.includes('*') ? true : this.options.cors.urls;\n\n this.fastifyServer.register(cors, {\n origin,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n // credentials: true,\n });\n }\n\n private configureMultipartUploads(): void {\n this.fastifyServer.register(multipart, {\n // attachFieldsToBody: true,\n limits: {\n fieldNameSize: 100,\n fieldSize: 1024 * 1024 * 10,\n fields: 10,\n fileSize: 1024 * 1024 * 1024 * 10, // 10GB file size limit\n files: 1,\n headerPairs: 2000,\n },\n });\n }\n\n /**\n * Configure routes.\n */\n private async configureRoutes(): Promise<void> {\n await this.loadRoutesFromDirectory();\n\n // Check if controllers directory exists\n const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory ?? '');\n\n if (!controllersDirectoryExists) {\n const routesRequiringControllers = this.routes.length === 0 || this.routes.some(route => !route.handler);\n\n if (routesRequiringControllers) {\n Logger.warn({\n message: 'Web server controllers directory not found',\n meta: {\n Directory: this.options.controllersDirectory,\n },\n });\n\n return;\n }\n }\n\n // Load controllers\n const controllers = controllersDirectoryExists\n ? await Loader.loadModulesInDirectory({\n directory: this.options.controllersDirectory,\n extensions: ['.ts', '.js'],\n })\n : {};\n\n // Add health check routes\n this.routes.push(\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/live',\n controller: WebServerHealthController,\n action: 'live',\n },\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/ready',\n controller: WebServerHealthController,\n action: 'ready',\n },\n );\n\n // Go through each route\n for (const route of this.routes) {\n let ControllerClass: WebServerBaseControllerType;\n\n let controllerName;\n\n if (route.handler && !route.controller && !route.controllerName) {\n if (route.type && route.type !== WebServerRouteType.Default) {\n throw new Error('Handler-only routes are only supported for default route type');\n }\n\n if (!('method' in route)) {\n throw new Error('Handler-only routes require an HTTP method');\n }\n\n const schema = this.buildFastifySchema(route.schema) ?? this.buildLegacySchema(route.validation);\n\n this.fastifyServer.route({\n method: route.method,\n url: route.path,\n handler: route.handler,\n ...(schema ? { schema } : {}),\n });\n\n continue;\n }\n\n if (route.controller) {\n ControllerClass = route.controller;\n\n controllerName = ControllerClass.name;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName] as WebServerBaseControllerType;\n\n controllerName = route.controllerName;\n } else {\n throw new Error('Web server controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n const controllerPath = `${this.options.controllersDirectory}/${route.controllerName}.ts`;\n\n Logger.warn({\n message: 'Web server controller not found',\n meta: {\n Controller: route.controllerName,\n Path: controllerPath,\n Route: `${route.path}`,\n },\n });\n\n continue;\n }\n\n // Initialize controller instance\n const controllerInstance = new ControllerClass({\n applicationConfig: this.applicationConfig,\n webServerOptions: this.options,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n eventManager: this.eventManager,\n databaseInstance: this.databaseInstance,\n lifecycleManager: this.lifecycleManager,\n });\n\n let routeMethod;\n let routeAction;\n let routePath;\n\n switch (route.type) {\n case WebServerRouteType.Default: {\n routeMethod = route.method;\n routeAction = route.action;\n routePath = route.path;\n\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeSchema: route.schema,\n handlerOverride: route.handler?.bind(controllerInstance),\n legacyValidation: route.validation,\n });\n\n break;\n }\n case WebServerRouteType.Entity: {\n if (this.applicationConfig.database && this.applicationConfig.database.enabled === true) {\n const entityModel = await Loader.loadEntityModule({\n entitiesDirectory: this.applicationConfig.database.entitiesDirectory,\n entityName: route.entityName,\n });\n\n const entityValidationSchema = (\n entityModel as { schema?: { describe: () => unknown } }\n ).schema?.describe() as\n | {\n keys: Record<string, { type: string; flags?: { presence?: string }; [key: string]: unknown }>;\n [key: string]: unknown;\n }\n | undefined;\n\n const formattedEntityValidationSchema = entityValidationSchema\n ? {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(entityValidationSchema.keys).map(([key, value]) => [key, { type: value.type }]),\n ),\n required: Object.keys(entityValidationSchema.keys).filter(\n // Dynamic schema inspection of joi describe output; keys are from trusted entity definitions\n // eslint-disable-next-line security/detect-object-injection\n key => entityValidationSchema.keys[key].flags?.presence === 'required',\n ),\n }\n : {};\n\n const entityRouteDefinitions = WebServerUtil.getEntityRouteDefinitions({\n basePath: route.path,\n entityValidationSchema: formattedEntityValidationSchema,\n });\n\n for (const entityRouteDefinition of entityRouteDefinitions) {\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod: entityRouteDefinition.method,\n routePath: entityRouteDefinition.path,\n routeAction: entityRouteDefinition.action,\n routeSchema: route.schema,\n handlerOverride: route.handler?.bind(controllerInstance),\n legacyValidation: entityRouteDefinition.validationSchema,\n });\n }\n }\n\n break;\n }\n }\n }\n\n if (this.options.debug?.printRoutes) {\n this.log('Routes:');\n\n console.log(this.fastifyServer.printRoutes());\n }\n }\n\n private async loadRoutesFromDirectory(): Promise<void> {\n const { routesDirectory } = this.options;\n\n if (!routesDirectory) {\n return;\n }\n\n const directoryExists = await File.pathExists(routesDirectory);\n\n if (!directoryExists) {\n this.logger.warn({\n message: 'Web server routes directory not found',\n meta: {\n Directory: routesDirectory,\n },\n });\n\n return;\n }\n\n const routeModules = await Loader.loadModulesInDirectory<\n WebServerRoute | WebServerRoute[] | { routes?: WebServerRoute[] }\n >({\n directory: routesDirectory,\n extensions: ['.ts', '.js'],\n });\n\n const loadedRoutes: WebServerRoute[] = [];\n\n for (const [moduleName, exportedRoutes] of Object.entries(routeModules)) {\n const normalizedRoutes = this.normalizeRouteExport(exportedRoutes, moduleName);\n\n if (normalizedRoutes.length === 0) {\n continue;\n }\n\n loadedRoutes.push(...normalizedRoutes);\n }\n\n if (loadedRoutes.length > 0) {\n this.routes.push(...loadedRoutes);\n }\n }\n\n private normalizeRouteExport(exportedValue: unknown, moduleName: string): WebServerRoute[] {\n const ensureRouteArray = (value: unknown): WebServerRoute[] => {\n if (Array.isArray(value)) {\n return value;\n }\n\n if (value && typeof value === 'object') {\n const maybeRoute = value as { routes?: unknown };\n\n if (Array.isArray(maybeRoute.routes)) {\n return maybeRoute.routes as WebServerRoute[];\n }\n }\n\n return value ? [value as WebServerRoute] : [];\n };\n\n const routeCandidates = ensureRouteArray(exportedValue);\n const validRoutes: WebServerRoute[] = [];\n\n for (const [index, candidate] of routeCandidates.entries()) {\n if (this.isValidRoute(candidate)) {\n validRoutes.push(candidate);\n } else {\n this.logger.warn({\n message: 'Invalid web server route definition skipped',\n meta: {\n Module: moduleName,\n Index: index,\n },\n });\n }\n }\n\n if (validRoutes.length === 0 && routeCandidates.length > 0) {\n this.logger.warn({\n message: 'No valid routes exported from module',\n meta: {\n Module: moduleName,\n },\n });\n }\n\n return validRoutes;\n }\n\n private isValidRoute(route: unknown): route is WebServerRoute {\n if (!route || typeof route !== 'object') {\n return false;\n }\n\n const candidate = route as Record<string, unknown>;\n const routePath = candidate.path;\n\n if (typeof routePath !== 'string' || routePath.length === 0) {\n return false;\n }\n\n const routeType = candidate.type ?? WebServerRouteType.Default;\n\n const controllerProvided =\n typeof candidate.controller === 'function' || typeof candidate.controllerName === 'string';\n\n if (routeType === WebServerRouteType.Entity || routeType === 'entity') {\n return controllerProvided && typeof candidate.entityName === 'string' && candidate.entityName.length > 0;\n }\n\n if (!controllerProvided) {\n return false;\n }\n\n const method = candidate.method;\n const action = candidate.action;\n\n const isValidMethod =\n typeof method === 'string' ||\n (Array.isArray(method) && method.length > 0 && method.every(m => typeof m === 'string'));\n\n const isValidAction = typeof action === 'string' && action.length > 0;\n\n return isValidMethod && isValidAction;\n }\n\n public async defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeSchema,\n handlerOverride,\n legacyValidation,\n }: {\n controllerInstance: any;\n controllerName: string;\n routeMethod: HTTPMethods | HTTPMethods[];\n routePath: string;\n routeAction?: string;\n routeSchema?: AnyRouteSchemaDefinition;\n handlerOverride?: ControllerAction<any>;\n legacyValidation?: {\n type: 'body' | 'query' | 'params';\n schema: { [key: string]: any };\n };\n }): Promise<void> {\n let handler = handlerOverride;\n\n if (!handler) {\n if (!routeAction) {\n throw new Error('Route action is required when handler override is not provided');\n }\n\n if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ['__proto__', 'prototype', 'constructor'].includes(routeAction)) {\n throw new Error('Invalid controller action name');\n }\n\n const controllerHandler = controllerInstance[routeAction as keyof typeof controllerInstance];\n\n if (!controllerHandler) {\n Logger.warn({\n message: 'Web server controller action not found',\n meta: {\n Controller: controllerName,\n Action: routeAction,\n },\n });\n\n throw new Error('Web server controller action not found');\n }\n\n handler = controllerHandler.bind(controllerInstance) as ControllerAction<any>;\n }\n\n const fastifySchema = this.buildFastifySchema(routeSchema) ?? this.buildLegacySchema(legacyValidation);\n\n if (!handler) {\n throw new Error('Route handler could not be resolved');\n }\n\n this.fastifyServer.route({\n method: routeMethod,\n url: routePath,\n handler: handler as unknown as (request: FastifyRequest, reply: FastifyReply) => unknown,\n ...(fastifySchema ? { schema: fastifySchema } : {}),\n });\n }\n\n /**\n * Start web server.\n */\n public async start(): Promise<void> {\n try {\n await this.fastifyServer.listen({\n host: this.options.host,\n port: this.options.port,\n });\n this._isReady = true;\n } catch (error) {\n Logger.error({ error });\n throw error;\n }\n }\n\n /**\n * Stop web server.\n */\n public async stop(): Promise<void> {\n this._isReady = false;\n // Close Fastify server\n await this.fastifyServer.close();\n }\n\n private buildFastifySchema(routeSchema?: AnyRouteSchemaDefinition): FastifySchema | undefined {\n if (!routeSchema) {\n return undefined;\n }\n\n const schema: FastifySchema = {};\n\n if (routeSchema.params) {\n schema.params = this.convertZodSchema(routeSchema.params);\n }\n\n if (routeSchema.querystring) {\n schema.querystring = this.convertZodSchema(routeSchema.querystring);\n }\n\n if (routeSchema.body) {\n schema.body = this.convertZodSchema(routeSchema.body);\n }\n\n if (routeSchema.headers) {\n schema.headers = this.convertZodSchema(routeSchema.headers);\n }\n\n if (routeSchema.response) {\n const responses = routeSchema.response as Record<string, z.ZodTypeAny>;\n const responseEntries = Object.entries(responses)\n .filter(([statusCode]) => /^[1-5][0-9]{2}$/.test(statusCode))\n .map(([statusCode, responseSchema]) => [statusCode, this.convertZodSchema(responseSchema)] as const);\n\n if (responseEntries.length > 0) {\n schema.response = Object.fromEntries(responseEntries);\n }\n }\n\n return schema;\n }\n\n private buildLegacySchema(legacyValidation?: {\n type: 'body' | 'query' | 'params';\n schema: { [key: string]: any };\n }): FastifySchema | undefined {\n if (!legacyValidation) {\n return undefined;\n }\n\n const schema: FastifySchema = {};\n\n switch (legacyValidation.type) {\n case 'body':\n schema.body = legacyValidation.schema;\n break;\n case 'query':\n schema.querystring = legacyValidation.schema;\n break;\n case 'params':\n schema.params = legacyValidation.schema;\n break;\n }\n\n return schema;\n }\n\n private convertZodSchema(zodSchema: z.ZodTypeAny) {\n const jsonSchema = toJSONSchema(zodSchema);\n\n if (jsonSchema && typeof jsonSchema === 'object' && '$schema' in jsonSchema) {\n const { $schema: _jsonSchemaVersion, ...rest } = jsonSchema as Record<string, unknown>;\n return rest;\n }\n\n return jsonSchema as Record<string, unknown>;\n }\n\n /**\n * Check if web server is ready to accept traffic.\n */\n public isReady(): boolean {\n return this._isReady && this.fastifyServer.server?.listening === true;\n }\n\n /**\n * Log web server message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'webServer', message, meta });\n }\n}\n\nexport default WebServer;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/websocket/websocket-base.ts"],
|
|
4
|
-
"sourcesContent": ["import type { WebSocketMessageHandler, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport { getRouteKey, log, parseServerMessage } from './utils.js';\nimport type { WebSocketServerBaseControllerType } from './controller/server/base.interface.js';\nimport type { WebSocketClientBaseControllerType } from './controller/client/base.interface.js';\nimport type WebSocket from 'ws';\nimport { File, Helper, Loader } from '../util/index.js';\n\nexport default abstract class WebSocketBase {\n protected routes: WebSocketRoute[] = [];\n protected routeHandlers: Map<string, WebSocketMessageHandler> = new Map();\n\n protected defaultRoutes: WebSocketRoute[] = [];\n\n public abstract get type(): WebSocketType;\n\n protected abstract getControllerDependencies():
|
|
5
|
-
"mappings": ";;AACA,SAAS,aAAa,KAAK,0BAA0B;AAIrD,SAAS,MAAM,QAAQ,cAAc;AAErC,MAAO,cAAqC;AAAA,EAP5C,OAO4C;AAAA;AAAA;AAAA,EAChC,SAA2B,CAAC;AAAA,EAC5B,gBAAsD,oBAAI,IAAI;AAAA,EAE9D,gBAAkC,CAAC;AAAA,EAQ7C,MAAgB,gBAAgB,QAA0B,sBAA6C;AAGrG,UAAM,6BAA6B,MAAM,KAAK,WAAW,oBAAoB;AAE7E,QAAI,CAAC,4BAA4B;AAC/B,UAAI,mCAAmC;AAAA,QACrC,WAAW;AAAA,MACb,CAAC;AAED;AAAA,IACF;AAEA,UAAM,sBAAsB,OAAO,uBAAuB;AAG1D,UAAM,cAAc,MAAM,OAAO,uBAAuB;AAAA,MACtD,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,YAAY,CAAC,OAAO,KAAK;AAAA,IAC3B,CAAC;AAED,eAAW,SAAS,QAAQ;AAC1B,UAAI;AAQJ,UAAI,MAAM,YAAY;AACpB,0BAAkB,MAAM;AAAA,MAC1B,WAAW,MAAM,gBAAgB;AAC/B,0BAAkB,YAAY,MAAM,cAAc;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { WebSocketMessageHandler, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport { getRouteKey, log, parseServerMessage } from './utils.js';\nimport type { WebSocketServerBaseControllerType } from './controller/server/base.interface.js';\nimport type { WebSocketClientBaseControllerType } from './controller/client/base.interface.js';\nimport type WebSocket from 'ws';\nimport { File, Helper, Loader } from '../util/index.js';\n\nexport default abstract class WebSocketBase {\n protected routes: WebSocketRoute[] = [];\n protected routeHandlers: Map<string, WebSocketMessageHandler> = new Map();\n\n protected defaultRoutes: WebSocketRoute[] = [];\n\n public abstract get type(): WebSocketType;\n\n protected abstract getControllerDependencies(): Record<string, unknown>;\n protected abstract shouldPrintRoutes(): boolean;\n protected abstract handleMessageError(clientId: string, error: string): void;\n\n protected async configureRoutes(routes: WebSocketRoute[], controllersDirectory: string): Promise<void> {\n // log ('Configuring routes', { Type: this.type, 'Controllers Directory': controllersDirectory });\n\n const controllersDirectoryExists = await File.pathExists(controllersDirectory);\n\n if (!controllersDirectoryExists) {\n log('Controllers directory not found', {\n Directory: controllersDirectory,\n });\n\n return;\n }\n\n const scriptFileExtension = Helper.getScriptFileExtension();\n\n // Load controllers\n const controllers = await Loader.loadModulesInDirectory({\n directory: controllersDirectory,\n // NOTE:\n // When getting \"system\", it gets /app/node_modules/@scpxl/nodejs-framework/dist/websocket/controllers/server\n // Therefor .js is needed also\n // Fix so only .ts vs .js is needed\n extensions: ['.ts', '.js'],\n });\n\n for (const route of routes) {\n let ControllerClass: WebSocketServerBaseControllerType | WebSocketClientBaseControllerType;\n\n // log('Registering route', {\n // Type: route.type,\n // Controller: route.controller ? route.controller.toString() : route.controllerName,\n // Action: route.action,\n // });\n\n if (route.controller) {\n ControllerClass = route.controller;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName] as\n | WebSocketServerBaseControllerType\n | WebSocketClientBaseControllerType;\n } else {\n throw new Error('WebSocket controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n log('Controller not found', {\n Controller: route.controllerName,\n Path: `${controllersDirectory}/${route.controllerName}.${scriptFileExtension}`,\n });\n\n continue;\n }\n\n const controllerDependencies = this.getControllerDependencies();\n\n const controllerInstance = new ControllerClass(controllerDependencies as any);\n\n const controllerHandler = controllerInstance[\n route.action as keyof typeof controllerInstance\n ] as WebSocketMessageHandler;\n const routeKey = getRouteKey(route.type, route.action);\n\n this.routeHandlers.set(routeKey, controllerHandler);\n }\n\n if (this.shouldPrintRoutes()) {\n log('Routes:', { Type: this.type });\n\n this.printRoutes();\n }\n }\n\n protected async handleServerMessage(\n ws: WebSocket,\n message: WebSocket.Data,\n clientId: string,\n ): Promise<{ type: unknown; action: unknown; response: unknown } | void> {\n try {\n const parsedMessage = parseServerMessage(message);\n const type = parsedMessage.type;\n const action = parsedMessage.action;\n\n log('Incoming message', {\n 'Client ID': clientId,\n Type: type ?? '-',\n Action: action ?? '-',\n });\n\n const routeKey = getRouteKey(parsedMessage.type as string, parsedMessage.action as string);\n\n const messageHandler = this.routeHandlers.get(routeKey);\n\n if (messageHandler) {\n const messageResponse = await messageHandler(ws, clientId, parsedMessage.data);\n\n return {\n type,\n action,\n response: messageResponse,\n };\n }\n // throw new Error(`Route handler not found (Route Key: ${routeKey} | Type: ${type} | Action: ${action})`);\n\n log('Route handler not found', {\n RouteKey: routeKey,\n Type: type,\n Action: action,\n });\n\n // if (\n // typeof this.applicationConfig.webSocket\n // ?.serverMessageHandler === 'function'\n // ) {\n // // Execute custom application subscriber event handler\n // this.applicationConfig.webSocket.serverMessageHandler(\n // {\n // ws,\n // clientId,\n // parsedMessage,\n // },\n // );\n // }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\n log(errorMessage);\n\n this.handleMessageError(clientId, errorMessage);\n }\n }\n\n protected printRoutes(): void {\n let routesString = '';\n\n const routeKeys = Array.from(this.routeHandlers.keys());\n\n routeKeys.forEach((routeKey, index) => {\n const [type, action] = routeKey.split(':');\n\n routesString += `Type: ${type} -> Action: ${action}`;\n\n if (index !== routeKeys.length - 1) {\n routesString += '\\n';\n }\n });\n\n log(routesString);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;AACA,SAAS,aAAa,KAAK,0BAA0B;AAIrD,SAAS,MAAM,QAAQ,cAAc;AAErC,MAAO,cAAqC;AAAA,EAP5C,OAO4C;AAAA;AAAA;AAAA,EAChC,SAA2B,CAAC;AAAA,EAC5B,gBAAsD,oBAAI,IAAI;AAAA,EAE9D,gBAAkC,CAAC;AAAA,EAQ7C,MAAgB,gBAAgB,QAA0B,sBAA6C;AAGrG,UAAM,6BAA6B,MAAM,KAAK,WAAW,oBAAoB;AAE7E,QAAI,CAAC,4BAA4B;AAC/B,UAAI,mCAAmC;AAAA,QACrC,WAAW;AAAA,MACb,CAAC;AAED;AAAA,IACF;AAEA,UAAM,sBAAsB,OAAO,uBAAuB;AAG1D,UAAM,cAAc,MAAM,OAAO,uBAAuB;AAAA,MACtD,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKX,YAAY,CAAC,OAAO,KAAK;AAAA,IAC3B,CAAC;AAED,eAAW,SAAS,QAAQ;AAC1B,UAAI;AAQJ,UAAI,MAAM,YAAY;AACpB,0BAAkB,MAAM;AAAA,MAC1B,WAAW,MAAM,gBAAgB;AAC/B,0BAAkB,YAAY,MAAM,cAAc;AAAA,MAGpD,OAAO;AACL,cAAM,IAAI,MAAM,uCAAuC;AAAA,MACzD;AAEA,UAAI,OAAO,oBAAoB,YAAY;AACzC,YAAI,wBAAwB;AAAA,UAC1B,YAAY,MAAM;AAAA,UAClB,MAAM,GAAG,oBAAoB,IAAI,MAAM,cAAc,IAAI,mBAAmB;AAAA,QAC9E,CAAC;AAED;AAAA,MACF;AAEA,YAAM,yBAAyB,KAAK,0BAA0B;AAE9D,YAAM,qBAAqB,IAAI,gBAAgB,sBAA6B;AAE5E,YAAM,oBAAoB,mBACxB,MAAM,MACR;AACA,YAAM,WAAW,YAAY,MAAM,MAAM,MAAM,MAAM;AAErD,WAAK,cAAc,IAAI,UAAU,iBAAiB;AAAA,IACpD;AAEA,QAAI,KAAK,kBAAkB,GAAG;AAC5B,UAAI,WAAW,EAAE,MAAM,KAAK,KAAK,CAAC;AAElC,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,MAAgB,oBACd,IACA,SACA,UACuE;AACvE,QAAI;AACF,YAAM,gBAAgB,mBAAmB,OAAO;AAChD,YAAM,OAAO,cAAc;AAC3B,YAAM,SAAS,cAAc;AAE7B,UAAI,oBAAoB;AAAA,QACtB,aAAa;AAAA,QACb,MAAM,QAAQ;AAAA,QACd,QAAQ,UAAU;AAAA,MACpB,CAAC;AAED,YAAM,WAAW,YAAY,cAAc,MAAgB,cAAc,MAAgB;AAEzF,YAAM,iBAAiB,KAAK,cAAc,IAAI,QAAQ;AAEtD,UAAI,gBAAgB;AAClB,cAAM,kBAAkB,MAAM,eAAe,IAAI,UAAU,cAAc,IAAI;AAE7E,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,UAAU;AAAA,QACZ;AAAA,MACF;AAGA,UAAI,2BAA2B;AAAA,QAC7B,UAAU;AAAA,QACV,MAAM;AAAA,QACN,QAAQ;AAAA,MACV,CAAC;AAAA,IAeH,SAAS,OAAO;AACd,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAE9D,UAAI,YAAY;AAEhB,WAAK,mBAAmB,UAAU,YAAY;AAAA,IAChD;AAAA,EACF;AAAA,EAEU,cAAoB;AAC5B,QAAI,eAAe;AAEnB,UAAM,YAAY,MAAM,KAAK,KAAK,cAAc,KAAK,CAAC;AAEtD,cAAU,QAAQ,CAAC,UAAU,UAAU;AACrC,YAAM,CAAC,MAAM,MAAM,IAAI,SAAS,MAAM,GAAG;AAEzC,sBAAgB,SAAS,IAAI,eAAe,MAAM;AAElD,UAAI,UAAU,UAAU,SAAS,GAAG;AAClC,wBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,QAAI,YAAY;AAAA,EAClB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/websocket/websocket-client.ts"],
|
|
4
|
-
"sourcesContent": ["import WebSocket, { type RawData } from 'ws';\nimport type { WebSocketOptions, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport type RedisInstance from '../redis/instance.js';\nimport type QueueManager from '../queue/manager.js';\nimport type DatabaseInstance from '../database/instance.js';\nimport type { WebSocketClientProps } from './websocket-client.interface.js';\nimport { generateClientId, log, parseServerMessage } from './utils.js';\nimport WebSocketBase from './websocket-base.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport path from 'path';\nimport { baseDir } from '../index.js';\n\nexport default class WebSocketClient extends WebSocketBase {\n protected defaultRoutes: WebSocketRoute[] = [\n {\n type: 'system',\n action: 'clientList',\n controllerName: 'system',\n },\n ];\n\n private applicationConfig: ApplicationConfig;\n private options: WebSocketOptions;\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private databaseInstance: DatabaseInstance;\n private ws?: WebSocket;\n private clientId?: string;\n private isConnected: boolean = false;\n\n constructor(props: WebSocketClientProps) {\n super();\n\n this.applicationConfig = props.applicationConfig;\n this.options = props.options;\n this.redisInstance = props.redisInstance;\n this.queueManager = props.queueManager;\n this.databaseInstance = props.databaseInstance;\n this.routes = props.routes;\n }\n\n public get type(): WebSocketType {\n return 'client';\n }\n\n public async load(): Promise<void> {\n const libraryControllersDirectory = path.join(baseDir, 'websocket', 'controllers', 'client');\n\n await this.configureRoutes(this.defaultRoutes, libraryControllersDirectory);\n\n await this.configureRoutes(this.routes, this.options.controllersDirectory);\n }\n\n public async connectToServer(): Promise<void> {\n const url = this.options.url;\n // const host = this.options.host;\n // const port = this.options.port;\n\n return new Promise(resolve => {\n const ws = new WebSocket(url);\n\n ws.on('open', () => {\n this.clientId = generateClientId();\n this.isConnected = true;\n\n log('Connected to server', { ID: this.clientId });\n\n if (this.options.events?.onConnected) {\n this.options.events.onConnected({\n ws,\n clientId: this.clientId,\n joinRoom: ({\n userId,\n userType,\n username,\n roomName,\n }: {\n userId?: string;\n userType?: string;\n username: string;\n roomName: string;\n }) => {\n this.sendClientMessage({\n type: 'system',\n action: 'joinRoom',\n data: {\n userId,\n userType,\n username,\n roomName,\n },\n });\n },\n });\n }\n\n resolve();\n });\n\n ws.on('message', this.handleIncomingMessage);\n\n ws.on('close', () => {\n this.isConnected = false;\n log('Connection to server closed');\n\n if (this.options.events?.onDisconnected) {\n this.options.events.onDisconnected({ clientId: this.clientId });\n }\n\n // Clean up event listeners to prevent memory leaks\n ws.removeAllListeners();\n this.ws = undefined;\n this.clientId = undefined;\n });\n\n ws.on('error', error => {\n log('WebSocket error', { error: error.message });\n\n if (this.options.events?.onError) {\n this.options.events.onError({ error });\n }\n });\n\n this.ws = ws;\n });\n }\n\n protected getControllerDependencies(): {\n sendMessage: (data: unknown) => void;\n redisInstance: RedisInstance;\n queueManager: QueueManager;\n databaseInstance: DatabaseInstance;\n } {\n return {\n sendMessage: this.sendMessage,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n };\n }\n protected shouldPrintRoutes(): boolean {\n return this.options.debug?.printRoutes ?? false;\n }\n\n private handleIncomingMessage = async (message: RawData): Promise<void> => {\n if (!this.ws || !this.clientId) {\n log('WebSocket not initialized or client ID not set');\n\n return;\n }\n\n if (this.options.events?.onMessage) {\n const parsedMessage = parseServerMessage(message);\n\n this.options.events.onMessage({\n ws: this.ws,\n clientId: this.clientId,\n data: parsedMessage,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n });\n }\n\n await this.handleServerMessage(this.ws, message, this.clientId);\n };\n\n protected handleMessageError(clientId: string, error: string): void {\n log(error);\n }\n\n public sendClientMessage = (data: unknown, binary: boolean = false): void => {\n if (!this.ws) {\n log('WebSocket not initialized');\n\n return;\n }\n\n const webSocketMessage = JSON.stringify(data);\n\n console.log('SENDING LCIENT MESSAGE: ', webSocketMessage);\n\n this.ws.send(webSocketMessage, { binary });\n };\n\n public sendMessage = (data: unknown): void => {\n this.sendClientMessage(data);\n };\n\n public disconnect(): void {\n if (this.ws && this.isConnected) {\n this.ws.removeAllListeners();\n this.ws.close();\n this.ws = undefined;\n this.clientId = undefined;\n this.isConnected = false;\n log('WebSocket client disconnected');\n }\n }\n\n public isClientConnected(): boolean {\n return this.isConnected && this.ws?.readyState === WebSocket.OPEN;\n }\n}\n"],
|
|
4
|
+
"sourcesContent": ["import WebSocket, { type RawData } from 'ws';\nimport type { WebSocketOptions, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport type RedisInstance from '../redis/instance.js';\nimport type QueueManager from '../queue/manager.js';\nimport type DatabaseInstance from '../database/instance.js';\nimport type { WebSocketClientProps } from './websocket-client.interface.js';\nimport { generateClientId, log, parseServerMessage } from './utils.js';\nimport WebSocketBase from './websocket-base.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport path from 'path';\nimport { baseDir } from '../index.js';\n\nexport default class WebSocketClient extends WebSocketBase {\n protected defaultRoutes: WebSocketRoute[] = [\n {\n type: 'system',\n action: 'clientList',\n controllerName: 'system',\n },\n ];\n\n private applicationConfig: ApplicationConfig;\n private options: WebSocketOptions;\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private databaseInstance: DatabaseInstance;\n private ws?: WebSocket;\n private clientId?: string;\n private isConnected: boolean = false;\n\n constructor(props: WebSocketClientProps) {\n super();\n\n this.applicationConfig = props.applicationConfig;\n this.options = props.options;\n this.redisInstance = props.redisInstance;\n this.queueManager = props.queueManager;\n this.databaseInstance = props.databaseInstance;\n this.routes = props.routes;\n }\n\n public get type(): WebSocketType {\n return 'client';\n }\n\n public async load(): Promise<void> {\n const libraryControllersDirectory = path.join(baseDir, 'websocket', 'controllers', 'client');\n\n await this.configureRoutes(this.defaultRoutes, libraryControllersDirectory);\n\n await this.configureRoutes(this.routes, this.options.controllersDirectory);\n }\n\n public async connectToServer(): Promise<void> {\n const url = this.options.url;\n // const host = this.options.host;\n // const port = this.options.port;\n\n return new Promise(resolve => {\n const ws = new WebSocket(url);\n\n ws.on('open', () => {\n this.clientId = generateClientId();\n this.isConnected = true;\n\n log('Connected to server', { ID: this.clientId });\n\n if (this.options.events?.onConnected) {\n this.options.events.onConnected({\n ws,\n clientId: this.clientId,\n joinRoom: ({\n userId,\n userType,\n username,\n roomName,\n }: {\n userId?: string;\n userType?: string;\n username: string;\n roomName: string;\n }) => {\n this.sendClientMessage({\n type: 'system',\n action: 'joinRoom',\n data: {\n userId,\n userType,\n username,\n roomName,\n },\n });\n },\n });\n }\n\n resolve();\n });\n\n ws.on('message', this.handleIncomingMessage);\n\n ws.on('close', () => {\n this.isConnected = false;\n log('Connection to server closed');\n\n if (this.options.events?.onDisconnected) {\n this.options.events.onDisconnected({ clientId: this.clientId });\n }\n\n // Clean up event listeners to prevent memory leaks\n ws.removeAllListeners();\n this.ws = undefined;\n this.clientId = undefined;\n });\n\n ws.on('error', error => {\n log('WebSocket error', { error: error.message });\n\n if (this.options.events?.onError) {\n this.options.events.onError({ error });\n }\n });\n\n this.ws = ws;\n });\n }\n\n protected getControllerDependencies(): {\n sendMessage: (data: unknown) => void;\n redisInstance: RedisInstance;\n queueManager: QueueManager;\n databaseInstance: DatabaseInstance;\n } {\n return {\n sendMessage: this.sendMessage,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n };\n }\n protected shouldPrintRoutes(): boolean {\n return this.options.debug?.printRoutes ?? false;\n }\n\n private handleIncomingMessage = async (message: RawData): Promise<void> => {\n if (!this.ws || !this.clientId) {\n log('WebSocket not initialized or client ID not set');\n\n return;\n }\n\n if (this.options.events?.onMessage) {\n const parsedMessage = parseServerMessage(message);\n\n this.options.events.onMessage({\n ws: this.ws,\n clientId: this.clientId,\n data: parsedMessage as { type: string; action: string; data: unknown },\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n });\n }\n\n await this.handleServerMessage(this.ws, message, this.clientId);\n };\n\n protected handleMessageError(clientId: string, error: string): void {\n log(error);\n }\n\n public sendClientMessage = (data: unknown, binary: boolean = false): void => {\n if (!this.ws) {\n log('WebSocket not initialized');\n\n return;\n }\n\n const webSocketMessage = JSON.stringify(data);\n\n console.log('SENDING LCIENT MESSAGE: ', webSocketMessage);\n\n this.ws.send(webSocketMessage, { binary });\n };\n\n public sendMessage = (data: unknown): void => {\n this.sendClientMessage(data);\n };\n\n public disconnect(): void {\n if (this.ws && this.isConnected) {\n this.ws.removeAllListeners();\n this.ws.close();\n this.ws = undefined;\n this.clientId = undefined;\n this.isConnected = false;\n log('WebSocket client disconnected');\n }\n }\n\n public isClientConnected(): boolean {\n return this.isConnected && this.ws?.readyState === WebSocket.OPEN;\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -211,14 +211,14 @@ class WebSocketServer extends WebSocketBase {
|
|
|
211
211
|
});
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
|
-
const
|
|
214
|
+
const includeSender = parsedMessage.includeSender === true;
|
|
215
215
|
const isSameWorker = parsedMessage.workerId === this.workerId;
|
|
216
|
-
if (
|
|
216
|
+
if (includeSender !== true && isSameWorker) {
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
log("Incoming subscriber message", {
|
|
220
220
|
Channel: channel,
|
|
221
|
-
// 'Run Same Worker': parsedMessage.
|
|
221
|
+
// 'Run Same Worker': parsedMessage.includeSender ? 'Yes' : 'No',
|
|
222
222
|
"Client ID": parsedMessage.clientId ?? "-"
|
|
223
223
|
});
|
|
224
224
|
switch (channel) {
|
|
@@ -440,7 +440,7 @@ class WebSocketServer extends WebSocketBase {
|
|
|
440
440
|
action: serverMessageResponse.action,
|
|
441
441
|
response: serverMessageResponse?.response
|
|
442
442
|
});
|
|
443
|
-
if (serverMessageResponse?.response
|
|
443
|
+
if (serverMessageResponse?.response && typeof serverMessageResponse.response === "object" && "error" in serverMessageResponse.response) {
|
|
444
444
|
Logger.error({ error: serverMessageResponse.response.error });
|
|
445
445
|
}
|
|
446
446
|
}
|
|
@@ -455,7 +455,7 @@ class WebSocketServer extends WebSocketBase {
|
|
|
455
455
|
this.redisInstance.publisherClient.publish(
|
|
456
456
|
WebSocketRedisSubscriberEvent.MessageError,
|
|
457
457
|
JSON.stringify({
|
|
458
|
-
|
|
458
|
+
includeSender: true,
|
|
459
459
|
clientId,
|
|
460
460
|
error
|
|
461
461
|
})
|