@scpxl/nodejs-framework 1.0.17 → 1.0.19
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/README.md +29 -0
- package/dist/api-requester/api-requester.d.ts +27 -6
- package/dist/api-requester/api-requester.d.ts.map +1 -1
- package/dist/api-requester/api-requester.js +188 -26
- package/dist/api-requester/api-requester.js.map +2 -2
- package/dist/api-requester/index.d.ts +1 -0
- package/dist/api-requester/index.d.ts.map +1 -1
- package/dist/api-requester/index.js.map +1 -1
- package/dist/application/base-application.d.ts +15 -22
- package/dist/application/base-application.d.ts.map +1 -1
- package/dist/application/base-application.js +145 -111
- package/dist/application/base-application.js.map +2 -2
- package/dist/application/command-application.d.ts.map +1 -1
- package/dist/application/command-application.js +3 -4
- package/dist/application/command-application.js.map +2 -2
- package/dist/application/web-application.d.ts.map +1 -1
- package/dist/application/web-application.js +9 -2
- package/dist/application/web-application.js.map +2 -2
- package/dist/cache/manager.d.ts +87 -6
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +77 -30
- package/dist/cache/manager.js.map +2 -2
- package/dist/cluster/cluster-manager.d.ts.map +1 -1
- package/dist/cluster/cluster-manager.js +5 -8
- package/dist/cluster/cluster-manager.js.map +2 -2
- package/dist/config/env.d.ts +11 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +103 -0
- package/dist/config/env.js.map +7 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +7 -0
- package/dist/config/schema.d.ts +408 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +218 -0
- package/dist/config/schema.js.map +7 -0
- package/dist/database/dynamic-entity.d.ts.map +1 -1
- package/dist/database/dynamic-entity.js +6 -2
- package/dist/database/dynamic-entity.js.map +2 -2
- package/dist/database/instance.d.ts.map +1 -1
- package/dist/database/instance.js +0 -8
- package/dist/database/instance.js.map +2 -2
- package/dist/database/manager.d.ts.map +1 -1
- package/dist/database/manager.js +71 -9
- package/dist/database/manager.js.map +2 -2
- package/dist/error/error-reporter.d.ts +96 -0
- package/dist/error/error-reporter.d.ts.map +1 -0
- package/dist/error/error-reporter.js +228 -0
- package/dist/error/error-reporter.js.map +7 -0
- package/dist/error/error.interface.d.ts +126 -0
- package/dist/error/error.interface.d.ts.map +1 -0
- package/dist/error/error.interface.js +45 -0
- package/dist/error/error.interface.js.map +7 -0
- package/dist/error/framework-errors.d.ts +113 -0
- package/dist/error/framework-errors.d.ts.map +1 -0
- package/dist/error/framework-errors.js +176 -0
- package/dist/error/framework-errors.js.map +7 -0
- package/dist/error/index.d.ts +6 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +34 -0
- package/dist/error/index.js.map +7 -0
- package/dist/event/manager.d.ts.map +1 -1
- package/dist/event/manager.js +2 -9
- package/dist/event/manager.js.map +2 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/lifecycle/exit.d.ts +2 -1
- package/dist/lifecycle/exit.d.ts.map +1 -1
- package/dist/lifecycle/exit.js +6 -0
- package/dist/lifecycle/exit.js.map +2 -2
- package/dist/lifecycle/index.d.ts +7 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +12 -0
- package/dist/lifecycle/index.js.map +7 -0
- package/dist/lifecycle/lifecycle-manager.d.ts +55 -2
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -1
- package/dist/lifecycle/lifecycle-manager.js +233 -7
- package/dist/lifecycle/lifecycle-manager.js.map +3 -3
- package/dist/lifecycle/shutdown-controller.d.ts +15 -0
- package/dist/lifecycle/shutdown-controller.d.ts.map +1 -0
- package/dist/lifecycle/shutdown-controller.js +38 -0
- package/dist/lifecycle/shutdown-controller.js.map +7 -0
- package/dist/lifecycle/types.d.ts +28 -0
- package/dist/lifecycle/types.d.ts.map +1 -0
- package/dist/lifecycle/types.js +13 -0
- package/dist/lifecycle/types.js.map +7 -0
- package/dist/logger/logger.d.ts +2 -1
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +35 -11
- package/dist/logger/logger.js.map +2 -2
- package/dist/performance/cache-performance.d.ts +6 -0
- package/dist/performance/cache-performance.d.ts.map +1 -1
- package/dist/performance/cache-performance.js +16 -0
- package/dist/performance/cache-performance.js.map +2 -2
- package/dist/performance/index.d.ts +1 -0
- package/dist/performance/index.d.ts.map +1 -1
- package/dist/performance/index.js +1 -0
- package/dist/performance/index.js.map +2 -2
- package/dist/performance/performance-monitor.d.ts.map +1 -1
- package/dist/performance/performance-monitor.js +47 -18
- package/dist/performance/performance-monitor.js.map +2 -2
- package/dist/performance/performance-monitor.plugin.d.ts +24 -0
- package/dist/performance/performance-monitor.plugin.d.ts.map +1 -0
- package/dist/performance/performance-monitor.plugin.js +89 -0
- package/dist/performance/performance-monitor.plugin.js.map +7 -0
- package/dist/performance/webserver-performance.js +1 -1
- package/dist/performance/webserver-performance.js.map +2 -2
- package/dist/queue/manager.d.ts.map +1 -1
- package/dist/queue/manager.js +3 -10
- package/dist/queue/manager.js.map +2 -2
- package/dist/queue/worker.d.ts.map +1 -1
- package/dist/queue/worker.js +2 -2
- package/dist/queue/worker.js.map +2 -2
- package/dist/redis/manager.d.ts.map +1 -1
- package/dist/redis/manager.js +228 -33
- package/dist/redis/manager.js.map +2 -2
- package/dist/request-context/index.d.ts +3 -0
- package/dist/request-context/index.d.ts.map +1 -0
- package/dist/request-context/index.js +25 -0
- package/dist/request-context/index.js.map +7 -0
- package/dist/request-context/request-context.d.ts +108 -0
- package/dist/request-context/request-context.d.ts.map +1 -0
- package/dist/request-context/request-context.interface.d.ts +46 -0
- package/dist/request-context/request-context.interface.d.ts.map +1 -0
- package/dist/request-context/request-context.interface.js +1 -0
- package/dist/request-context/request-context.interface.js.map +7 -0
- package/dist/request-context/request-context.js +79 -0
- package/dist/request-context/request-context.js.map +7 -0
- package/dist/services/aws/s3.js +4 -6
- package/dist/services/aws/s3.js.map +2 -2
- package/dist/util/file.d.ts +13 -0
- package/dist/util/file.d.ts.map +1 -1
- package/dist/util/file.js +46 -9
- package/dist/util/file.js.map +2 -2
- package/dist/util/helper.d.ts +16 -1
- package/dist/util/helper.d.ts.map +1 -1
- package/dist/util/helper.js +19 -43
- package/dist/util/helper.js.map +2 -2
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.d.ts.map +1 -1
- package/dist/util/index.js +18 -16
- package/dist/util/index.js.map +2 -2
- package/dist/util/loader.d.ts.map +1 -1
- package/dist/util/loader.js +13 -2
- package/dist/util/loader.js.map +2 -2
- package/dist/util/os.d.ts.map +1 -1
- package/dist/util/os.js +8 -14
- package/dist/util/os.js.map +2 -2
- package/dist/util/time.d.ts +8 -2
- package/dist/util/time.d.ts.map +1 -1
- package/dist/util/time.js +12 -7
- package/dist/util/time.js.map +2 -2
- package/dist/util/timing.d.ts +36 -0
- package/dist/util/timing.d.ts.map +1 -0
- package/dist/util/timing.interface.d.ts +47 -0
- package/dist/util/timing.interface.d.ts.map +1 -0
- package/dist/util/timing.interface.js +1 -0
- package/dist/util/timing.interface.js.map +7 -0
- package/dist/util/timing.js +98 -0
- package/dist/util/timing.js.map +7 -0
- package/dist/util/url.js +1 -1
- package/dist/util/url.js.map +2 -2
- package/dist/webserver/controller/base.d.ts +3 -1
- package/dist/webserver/controller/base.d.ts.map +1 -1
- package/dist/webserver/controller/base.interface.d.ts +2 -0
- package/dist/webserver/controller/base.interface.d.ts.map +1 -1
- package/dist/webserver/controller/base.js +4 -1
- package/dist/webserver/controller/base.js.map +2 -2
- package/dist/webserver/controller/health.d.ts +8 -1
- package/dist/webserver/controller/health.d.ts.map +1 -1
- package/dist/webserver/controller/health.js +36 -22
- package/dist/webserver/controller/health.js.map +2 -2
- package/dist/webserver/webserver.d.ts +16 -2
- package/dist/webserver/webserver.d.ts.map +1 -1
- package/dist/webserver/webserver.interface.d.ts +37 -0
- package/dist/webserver/webserver.interface.d.ts.map +1 -1
- package/dist/webserver/webserver.interface.js.map +2 -2
- package/dist/webserver/webserver.js +117 -20
- package/dist/webserver/webserver.js.map +2 -2
- package/dist/websocket/controllers/server/system.d.ts.map +1 -1
- package/dist/websocket/controllers/server/system.js.map +2 -2
- package/dist/websocket/websocket-base.d.ts.map +1 -1
- package/dist/websocket/websocket-base.js +2 -3
- package/dist/websocket/websocket-base.js.map +2 -2
- package/dist/websocket/websocket-server.d.ts +1 -1
- package/dist/websocket/websocket-server.d.ts.map +1 -1
- package/dist/websocket/websocket-server.js +7 -31
- package/dist/websocket/websocket-server.js.map +2 -2
- package/package.json +51 -10
|
@@ -13,9 +13,9 @@ import CacheManager from "../cache/manager.js";
|
|
|
13
13
|
import os from "os";
|
|
14
14
|
import EventManager from "../event/manager.js";
|
|
15
15
|
import Logger from "../logger/logger.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { PerformanceMonitorPlugin } from "../performance/performance-monitor.plugin.js";
|
|
17
|
+
import { LifecycleManager, ShutdownController } from "../lifecycle/index.js";
|
|
18
|
+
import { ConfigValidationError, formatConfigIssues, validateFrameworkConfig } from "../config/schema.js";
|
|
19
19
|
import { requestExit } from "../lifecycle/exit.js";
|
|
20
20
|
class BaseApplication {
|
|
21
21
|
static {
|
|
@@ -23,12 +23,8 @@ class BaseApplication {
|
|
|
23
23
|
}
|
|
24
24
|
/** Unique instance ID */
|
|
25
25
|
uniqueInstanceId;
|
|
26
|
-
/** Shutdown signals */
|
|
27
|
-
shutdownSignals = ["SIGTERM", "SIGINT"];
|
|
28
26
|
/** Application start time */
|
|
29
|
-
startTime =
|
|
30
|
-
/** Whether application is stopping */
|
|
31
|
-
isStopping = false;
|
|
27
|
+
startTime = 0;
|
|
32
28
|
/** Shutdown timeout (30 seconds) */
|
|
33
29
|
shutdownTimeout = 3e4;
|
|
34
30
|
/** Cache for application version to avoid repeated imports */
|
|
@@ -52,7 +48,9 @@ class BaseApplication {
|
|
|
52
48
|
/** Performance monitor */
|
|
53
49
|
performanceMonitor;
|
|
54
50
|
/** Lifecycle manager */
|
|
55
|
-
lifecycle
|
|
51
|
+
lifecycle;
|
|
52
|
+
/** Shutdown controller */
|
|
53
|
+
shutdownController;
|
|
56
54
|
get Name() {
|
|
57
55
|
return this.config.name;
|
|
58
56
|
}
|
|
@@ -60,9 +58,32 @@ class BaseApplication {
|
|
|
60
58
|
* Application constructor
|
|
61
59
|
*/
|
|
62
60
|
constructor(config) {
|
|
61
|
+
try {
|
|
62
|
+
const validated = validateFrameworkConfig(config);
|
|
63
|
+
config = validated;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err instanceof ConfigValidationError) {
|
|
66
|
+
const formatted = formatConfigIssues(err.issues);
|
|
67
|
+
throw new Error(`Configuration validation failed:
|
|
68
|
+
${formatted}`);
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
63
72
|
const computerName = os.hostname();
|
|
64
73
|
this.uniqueInstanceId = `${config.instanceId}-${computerName}-${OS.getUniqueComputerId()}`;
|
|
65
74
|
this.config = config;
|
|
75
|
+
const lifecycleConfig = {
|
|
76
|
+
gracefulShutdown: {
|
|
77
|
+
timeoutMs: this.shutdownTimeout
|
|
78
|
+
},
|
|
79
|
+
readiness: {
|
|
80
|
+
timeoutMs: 3e4,
|
|
81
|
+
checkIntervalMs: 100
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
this.lifecycle = new LifecycleManager(lifecycleConfig);
|
|
85
|
+
this.shutdownController = new ShutdownController(this.lifecycle);
|
|
86
|
+
this.registerShutdownHooks();
|
|
66
87
|
this.redisManager = new RedisManager({
|
|
67
88
|
applicationConfig: this.config,
|
|
68
89
|
host: this.config.redis.host,
|
|
@@ -70,10 +91,9 @@ class BaseApplication {
|
|
|
70
91
|
password: this.config.redis.password
|
|
71
92
|
});
|
|
72
93
|
this.cacheManager = new CacheManager({
|
|
73
|
-
applicationConfig: this.config,
|
|
74
94
|
redisManager: this.redisManager
|
|
75
95
|
});
|
|
76
|
-
|
|
96
|
+
PerformanceMonitorPlugin.register(this);
|
|
77
97
|
this.setupGlobalErrorHandlers();
|
|
78
98
|
if (this.config.database && this.config.database.enabled === true) {
|
|
79
99
|
const defaultEntitiesDirectory = join(this.config.rootDirectory, "src", "database", "entities");
|
|
@@ -116,35 +136,12 @@ class BaseApplication {
|
|
|
116
136
|
* Start application
|
|
117
137
|
*/
|
|
118
138
|
async start() {
|
|
119
|
-
this.startTime =
|
|
139
|
+
this.startTime = Time.now();
|
|
120
140
|
this.applicationVersion = await this.getApplicationVersion();
|
|
121
141
|
const startInstanceOptions = {
|
|
122
|
-
// onStarted: ({ startupTime }) => {
|
|
123
|
-
// if (this.config.log?.startUp) {
|
|
124
|
-
// Logger.info('Application started', {
|
|
125
|
-
// Name: this.config.name,
|
|
126
|
-
// 'PXL Framework Version': this.applicationVersion,
|
|
127
|
-
// 'Startup Time': Time.formatTime({ time: startupTime, format: 's', numDecimals: 2, showUnit: true }),
|
|
128
|
-
// });
|
|
129
|
-
// }
|
|
130
|
-
// if (this.config.events?.onStarted) {
|
|
131
|
-
// this.config.events.onStarted({ app: this, startupTime });
|
|
132
|
-
// }
|
|
133
|
-
// },
|
|
134
142
|
onStarted: this.onStarted.bind(this)
|
|
135
143
|
};
|
|
136
144
|
const stopInstanceOptions = {
|
|
137
|
-
// onStopped: ({ runtime }) => {
|
|
138
|
-
// if (this.config.log?.shutdown) {
|
|
139
|
-
// Logger.info('Application stopped', {
|
|
140
|
-
// Name: this.config.name,
|
|
141
|
-
// 'Runtime': Time.formatTime({ time: runtime, format: 's', numDecimals: 2, showUnit: true }),
|
|
142
|
-
// });
|
|
143
|
-
// }
|
|
144
|
-
// if (this.config.events?.onStopped) {
|
|
145
|
-
// this.config.events.onStopped({ app: this, runtime });
|
|
146
|
-
// }
|
|
147
|
-
// },
|
|
148
145
|
onStopped: this.onStopped.bind(this)
|
|
149
146
|
};
|
|
150
147
|
if (this.config.cluster?.enabled) {
|
|
@@ -156,9 +153,6 @@ class BaseApplication {
|
|
|
156
153
|
clusterManager.start();
|
|
157
154
|
} else {
|
|
158
155
|
await this.startInstance(startInstanceOptions);
|
|
159
|
-
this.handleShutdown({
|
|
160
|
-
onStopped: stopInstanceOptions.onStopped
|
|
161
|
-
});
|
|
162
156
|
}
|
|
163
157
|
}
|
|
164
158
|
/**
|
|
@@ -192,6 +186,22 @@ class BaseApplication {
|
|
|
192
186
|
await queueManager.registerQueues({
|
|
193
187
|
queues: this.config.queue.queues
|
|
194
188
|
});
|
|
189
|
+
this.lifecycle.addReadinessCheck("redis", async () => {
|
|
190
|
+
try {
|
|
191
|
+
return await redisInstance.isConnected();
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
if (databaseInstance) {
|
|
197
|
+
this.lifecycle.addReadinessCheck("database", async () => {
|
|
198
|
+
try {
|
|
199
|
+
return await databaseInstance.isConnected();
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
195
205
|
return {
|
|
196
206
|
redisInstance,
|
|
197
207
|
databaseInstance,
|
|
@@ -209,28 +219,40 @@ class BaseApplication {
|
|
|
209
219
|
*/
|
|
210
220
|
onStopped({ runtime: _runtime }) {
|
|
211
221
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Before application stop event
|
|
214
|
-
*/
|
|
215
|
-
async onBeforeStop() {
|
|
216
|
-
await this.redisManager.disconnect();
|
|
217
|
-
if (this.databaseManager) {
|
|
218
|
-
await this.databaseManager.disconnect();
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
222
|
/**
|
|
222
223
|
* Start application instance
|
|
223
224
|
*/
|
|
224
225
|
async startInstance(options) {
|
|
225
226
|
try {
|
|
227
|
+
const initResult = await this.lifecycle.initialize();
|
|
228
|
+
if (initResult.errors.length > 0) {
|
|
229
|
+
Logger.warn({
|
|
230
|
+
message: "Lifecycle init phase encountered errors",
|
|
231
|
+
meta: { errors: initResult.errors.map((e) => e instanceof Error ? e.message : String(e)) }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
226
234
|
const { redisInstance, databaseInstance, queueManager, eventManager } = await this.onBeforeStart();
|
|
235
|
+
const startResult = await this.lifecycle.start();
|
|
236
|
+
if (startResult.errors.length > 0) {
|
|
237
|
+
Logger.warn({
|
|
238
|
+
message: "Lifecycle start phase encountered errors",
|
|
239
|
+
meta: { errors: startResult.errors.map((e) => e instanceof Error ? e.message : String(e)) }
|
|
240
|
+
});
|
|
241
|
+
}
|
|
227
242
|
await this.startHandler({
|
|
228
243
|
redisInstance,
|
|
229
244
|
databaseInstance,
|
|
230
245
|
queueManager,
|
|
231
246
|
eventManager
|
|
232
247
|
});
|
|
233
|
-
const
|
|
248
|
+
const readyResult = await this.lifecycle.ready();
|
|
249
|
+
if (readyResult.errors.length > 0) {
|
|
250
|
+
Logger.warn({
|
|
251
|
+
message: "Lifecycle ready phase encountered errors",
|
|
252
|
+
meta: { errors: readyResult.errors.map((e) => e instanceof Error ? e.message : String(e)) }
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const startupTime = Time.calculateElapsedTimeMs({
|
|
234
256
|
startTime: this.startTime
|
|
235
257
|
});
|
|
236
258
|
if (options.onStarted) {
|
|
@@ -250,38 +272,8 @@ class BaseApplication {
|
|
|
250
272
|
/**
|
|
251
273
|
* Initialize performance monitor
|
|
252
274
|
*/
|
|
253
|
-
initializePerformanceMonitor
|
|
254
|
-
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
this.performanceMonitor = PerformanceMonitor.initialize({
|
|
258
|
-
enabled: true,
|
|
259
|
-
thresholds: this.config.performanceMonitoring.thresholds,
|
|
260
|
-
maxMetricsHistory: this.config.performanceMonitoring.maxMetricsHistory,
|
|
261
|
-
logSlowOperations: this.config.performanceMonitoring.logSlowOperations,
|
|
262
|
-
logAllOperations: this.config.performanceMonitoring.logAllOperations
|
|
263
|
-
});
|
|
264
|
-
if (this.config.performanceMonitoring.monitorDatabaseOperations !== false) {
|
|
265
|
-
DatabasePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
266
|
-
}
|
|
267
|
-
if (this.config.performanceMonitoring.monitorQueueOperations !== false) {
|
|
268
|
-
QueuePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
269
|
-
}
|
|
270
|
-
if (this.config.performanceMonitoring.monitorCacheOperations !== false) {
|
|
271
|
-
CachePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
272
|
-
}
|
|
273
|
-
if (this.config.performanceMonitoring?.reportInterval && this.config.performanceMonitoring.reportInterval > 0) {
|
|
274
|
-
this.lifecycle.trackInterval(
|
|
275
|
-
setInterval(() => {
|
|
276
|
-
const reportFormat = this.config.performanceMonitoring?.reportFormat ?? "detailed";
|
|
277
|
-
const report = this.performanceMonitor?.generateFormattedReport(reportFormat);
|
|
278
|
-
if (report) {
|
|
279
|
-
Logger.info({ message: report });
|
|
280
|
-
}
|
|
281
|
-
}, this.config.performanceMonitoring.reportInterval)
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
275
|
+
// initializePerformanceMonitor deprecated in favor of PerformanceMonitorPlugin
|
|
276
|
+
// (left intentionally absent)
|
|
285
277
|
setupGlobalErrorHandlers() {
|
|
286
278
|
process.on("uncaughtException", (error) => {
|
|
287
279
|
Logger.error({ error, message: "Uncaught Exception" });
|
|
@@ -296,61 +288,103 @@ class BaseApplication {
|
|
|
296
288
|
this.initiateGracefulShutdown();
|
|
297
289
|
});
|
|
298
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Register shutdown hooks for proper cleanup
|
|
293
|
+
*/
|
|
294
|
+
registerShutdownHooks() {
|
|
295
|
+
this.lifecycle.onShutdown(async () => {
|
|
296
|
+
Logger.info({ message: "Executing custom stop callback" });
|
|
297
|
+
await this.stopCallback();
|
|
298
|
+
});
|
|
299
|
+
this.lifecycle.onShutdown(async () => {
|
|
300
|
+
if (this.redisManager) {
|
|
301
|
+
Logger.info({ message: "Disconnecting from Redis" });
|
|
302
|
+
await this.redisManager.disconnect();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
this.lifecycle.onShutdown(async () => {
|
|
306
|
+
if (this.databaseManager) {
|
|
307
|
+
Logger.info({ message: "Disconnecting from database" });
|
|
308
|
+
await this.databaseManager.disconnect();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
299
312
|
/**
|
|
300
313
|
* Initiate graceful shutdown
|
|
301
314
|
*/
|
|
302
|
-
initiateGracefulShutdown() {
|
|
303
|
-
if (this.
|
|
315
|
+
async initiateGracefulShutdown() {
|
|
316
|
+
if (this.shutdownController.isShuttingDown) {
|
|
304
317
|
return;
|
|
305
318
|
}
|
|
306
319
|
Logger.info({ message: "Initiating graceful shutdown due to error" });
|
|
307
|
-
|
|
320
|
+
try {
|
|
321
|
+
const result = await this.shutdownController.initiate("error-triggered");
|
|
322
|
+
if (result.errors.length > 0) {
|
|
323
|
+
Logger.error({
|
|
324
|
+
message: "Errors during shutdown",
|
|
325
|
+
error: result.errors
|
|
326
|
+
});
|
|
327
|
+
this.finalizeExit({ code: 1, reason: "graceful-shutdown-error", error: result.errors });
|
|
328
|
+
} else if (result.timedOut) {
|
|
329
|
+
Logger.warn({ message: "Shutdown timed out" });
|
|
330
|
+
this.finalizeExit({ code: 1, reason: "shutdown-timeout" });
|
|
331
|
+
} else {
|
|
332
|
+
this.finalizeExit({ code: 0, reason: "error-shutdown-complete" });
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
308
335
|
Logger.error({
|
|
309
336
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
310
337
|
message: "Error during graceful shutdown"
|
|
311
338
|
});
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Handle shutdown
|
|
317
|
-
*/
|
|
318
|
-
handleShutdown({ onStopped }) {
|
|
319
|
-
this.shutdownSignals.forEach((signal) => {
|
|
320
|
-
process.on(signal, async () => {
|
|
321
|
-
await this.stop({ onStopped });
|
|
322
|
-
});
|
|
323
|
-
});
|
|
339
|
+
this.finalizeExit({ code: 1, reason: "graceful-shutdown-error", error });
|
|
340
|
+
}
|
|
324
341
|
}
|
|
325
342
|
/**
|
|
326
|
-
* Stop application
|
|
343
|
+
* Stop application using lifecycle manager
|
|
327
344
|
*/
|
|
328
345
|
async stop({ onStopped } = {}) {
|
|
329
|
-
if (this.
|
|
346
|
+
if (this.shutdownController.isShuttingDown) {
|
|
330
347
|
return;
|
|
331
348
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Logger.warn({ message: "Forced shutdown due to timeout" });
|
|
335
|
-
requestExit({ code: 1, reason: "forced-timeout" });
|
|
336
|
-
}, this.shutdownTimeout);
|
|
337
|
-
try {
|
|
338
|
-
await this.stopCallback();
|
|
339
|
-
await this.onBeforeStop();
|
|
340
|
-
if (onStopped) {
|
|
349
|
+
if (onStopped) {
|
|
350
|
+
this.lifecycle.onShutdown(() => {
|
|
341
351
|
const runtime = process.uptime() * 1e3;
|
|
342
|
-
|
|
352
|
+
onStopped({ runtime });
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const result = await this.shutdownController.initiate("manual-stop");
|
|
357
|
+
if (result.errors.length > 0) {
|
|
358
|
+
Logger.error({
|
|
359
|
+
message: "Errors during shutdown",
|
|
360
|
+
error: result.errors
|
|
361
|
+
});
|
|
362
|
+
this.finalizeExit({ code: 1, reason: "shutdown-error", error: result.errors });
|
|
363
|
+
} else if (result.timedOut) {
|
|
364
|
+
Logger.warn({ message: "Shutdown timed out" });
|
|
365
|
+
this.finalizeExit({ code: 1, reason: "shutdown-timeout" });
|
|
366
|
+
} else {
|
|
367
|
+
this.finalizeExit({ code: 0, reason: "shutdown-complete" });
|
|
343
368
|
}
|
|
344
|
-
clearTimeout(forceExitTimeout);
|
|
345
|
-
requestExit({ code: 0, reason: "shutdown-complete" });
|
|
346
369
|
} catch (error) {
|
|
347
370
|
Logger.error({
|
|
348
371
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
349
372
|
message: "Error during shutdown"
|
|
350
373
|
});
|
|
351
|
-
|
|
352
|
-
|
|
374
|
+
this.finalizeExit({ code: 1, reason: "shutdown-error", error });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Finalize exit: during tests, suppress actual process exit to avoid failing vitest runs.
|
|
379
|
+
*/
|
|
380
|
+
finalizeExit(outcome) {
|
|
381
|
+
const nodeEnv = process.env.NODE_ENV ?? "";
|
|
382
|
+
const isTestEnv = nodeEnv.toLowerCase() === "test" || "VITEST" in process.env || "VITEST_WORKER_ID" in process.env || process.argv.some((a) => a.includes("vitest")) || typeof globalThis.afterAll === "function";
|
|
383
|
+
if (isTestEnv) {
|
|
384
|
+
Logger.info({ message: `Skipping process exit in test environment (${outcome.reason})`, code: outcome.code });
|
|
385
|
+
return;
|
|
353
386
|
}
|
|
387
|
+
requestExit(outcome);
|
|
354
388
|
}
|
|
355
389
|
}
|
|
356
390
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/application/base-application.ts"],
|
|
4
|
-
"sourcesContent": ["import cluster from 'cluster';\nimport { existsSync, readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join, resolve } from 'path';\nimport { type DatabaseInstance, DatabaseManager } from '../database/index.js';\nimport QueueManager from '../queue/manager.js';\nimport RedisManager from '../redis/manager.js';\nimport type {\n ApplicationConfig,\n ApplicationStartInstanceOptions,\n ApplicationStopInstanceOptions,\n} from './base-application.interface.js';\nimport ClusterManager from '../cluster/cluster-manager.js';\nimport type RedisInstance from '../redis/instance.js';\nimport { OS, Time } from '../util/index.js';\nimport CacheManager from '../cache/manager.js';\nimport os from 'os';\nimport EventManager from '../event/manager.js';\nimport Logger from '../logger/logger.js';\nimport { PerformanceMonitor } from '../performance/performance-monitor.js';\nimport { CachePerformanceWrapper, DatabasePerformanceWrapper, QueuePerformanceWrapper } from '../performance/index.js';\nimport { LifecycleManager } from '../lifecycle/lifecycle-manager.js';\nimport { requestExit } from '../lifecycle/exit.js';\n\n// Re-export types for external use\nexport type { ApplicationConfig } from './base-application.interface.js';\n\nexport default abstract class BaseApplication {\n /** Unique instance ID */\n public uniqueInstanceId: string;\n\n /** Shutdown signals */\n protected shutdownSignals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];\n\n /** Application start time */\n protected startTime: [number, number] = [0, 0];\n\n /** Whether application is stopping */\n protected isStopping = false;\n\n /** Shutdown timeout (30 seconds) */\n protected shutdownTimeout = 30000;\n\n /** Cache for application version to avoid repeated imports */\n private static applicationVersionCache: string | undefined;\n\n /** Cluster worker ID */\n protected workerId = cluster.isWorker && cluster.worker ? cluster.worker.id : null;\n\n /** Application config */\n protected config: ApplicationConfig;\n\n /** Application version */\n protected applicationVersion?: string;\n\n /** Redis manager */\n public redisManager: RedisManager;\n\n /** Cache manager */\n public cacheManager: CacheManager;\n\n /** Database manager */\n public databaseManager?: DatabaseManager;\n\n /** Queue manager */\n public queueManager?: QueueManager;\n\n /** Event manager */\n public eventManager?: EventManager;\n\n /** Performance monitor */\n public performanceMonitor?: PerformanceMonitor;\n\n /** Lifecycle manager */\n protected lifecycle: LifecycleManager = new LifecycleManager();\n\n public get Name() {\n return this.config.name;\n }\n\n /**\n * Application constructor\n */\n constructor(config: ApplicationConfig) {\n const computerName = os.hostname();\n\n this.uniqueInstanceId = `${config.instanceId}-${computerName}-${OS.getUniqueComputerId()}`;\n\n this.config = config;\n\n // const schema = Joi.object({\n // name: Joi.string().required(),\n\n // redis: {\n // host: Joi.string().required(),\n // port: Joi.number().required(),\n // password: Joi.string().allow('').optional(),\n // },\n\n // database: {\n // host: Joi.string().required(),\n // port: Joi.number().required(),\n // username: Joi.string().required(),\n // password: Joi.string().required(),\n // databaseName: Joi.string().required(),\n // },\n // });\n\n // // Validation application constructor props\n // const validationResult = schema.validate(props);\n\n // if (validationResult.error) {\n // throw new Error(validationResult.error.message);\n // }\n\n // Initialize Redis manager\n this.redisManager = new RedisManager({\n applicationConfig: this.config,\n host: this.config.redis.host,\n port: this.config.redis.port,\n password: this.config.redis.password,\n });\n\n // Initialize cache manager\n this.cacheManager = new CacheManager({\n applicationConfig: this.config,\n redisManager: this.redisManager,\n });\n\n // Initialize performance monitor\n this.initializePerformanceMonitor();\n\n // Set up global error handlers\n this.setupGlobalErrorHandlers();\n\n if (this.config.database && this.config.database.enabled === true) {\n const defaultEntitiesDirectory = join(this.config.rootDirectory, 'src', 'database', 'entities');\n\n if (!this.config.database.entitiesDirectory) {\n this.config.database.entitiesDirectory = defaultEntitiesDirectory;\n }\n\n if (!existsSync(this.config.database.entitiesDirectory)) {\n throw new Error(`Database entities directory not found (Path: ${this.config.database.entitiesDirectory})`);\n }\n\n // Initialize Database manager\n this.databaseManager = new DatabaseManager({\n applicationConfig: this.config,\n host: this.config.database.host,\n port: this.config.database.port,\n username: this.config.database.username,\n password: this.config.database.password,\n databaseName: this.config.database.databaseName,\n entitiesDirectory: this.config.database.entitiesDirectory,\n });\n }\n }\n\n /**\n * Get application version\n */\n public async getApplicationVersion(): Promise<string> {\n // Return cached version if available\n if (BaseApplication.applicationVersionCache !== undefined) {\n return BaseApplication.applicationVersionCache;\n }\n\n // Resolve the path to package.json\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const packageJsonPath = resolve(__dirname, '../../package.json');\n\n // Read and parse the file\n const fileContents = readFileSync(packageJsonPath, 'utf-8');\n const packageJson = JSON.parse(fileContents);\n\n if (!packageJson?.version) {\n throw new Error('Application version not found');\n }\n\n // Cache and return the version\n BaseApplication.applicationVersionCache = packageJson.version;\n\n return packageJson.version;\n }\n\n /**\n * Start application\n */\n public async start(): Promise<void> {\n // Start application timer\n this.startTime = process.hrtime();\n\n // Get application version`\n this.applicationVersion = await this.getApplicationVersion();\n\n const startInstanceOptions: ApplicationStartInstanceOptions = {\n // onStarted: ({ startupTime }) => {\n // if (this.config.log?.startUp) {\n // Logger.info('Application started', {\n // Name: this.config.name,\n // 'PXL Framework Version': this.applicationVersion,\n // 'Startup Time': Time.formatTime({ time: startupTime, format: 's', numDecimals: 2, showUnit: true }),\n // });\n // }\n\n // if (this.config.events?.onStarted) {\n // this.config.events.onStarted({ app: this, startupTime });\n // }\n // },\n onStarted: this.onStarted.bind(this),\n };\n\n const stopInstanceOptions: ApplicationStopInstanceOptions = {\n // onStopped: ({ runtime }) => {\n // if (this.config.log?.shutdown) {\n // Logger.info('Application stopped', {\n // Name: this.config.name,\n // 'Runtime': Time.formatTime({ time: runtime, format: 's', numDecimals: 2, showUnit: true }),\n // });\n // }\n\n // if (this.config.events?.onStopped) {\n // this.config.events.onStopped({ app: this, runtime });\n // }\n // },\n onStopped: this.onStopped.bind(this),\n };\n\n if (this.config.cluster?.enabled) {\n // Initialize clustered application\n const clusterManager = new ClusterManager({\n config: this.config.cluster,\n\n startApplicationCallback: () => this.startInstance(startInstanceOptions),\n stopApplicationCallback: () => this.stop(stopInstanceOptions),\n });\n\n // Start cluster\n clusterManager.start();\n } else {\n // Start standalone application\n await this.startInstance(startInstanceOptions);\n\n // Handle standalone application shutdown\n this.handleShutdown({\n onStopped: stopInstanceOptions.onStopped,\n });\n }\n }\n\n /**\n * Before application start\n */\n private async onBeforeStart(): Promise<{\n redisInstance: RedisInstance;\n databaseInstance: DatabaseInstance | null;\n queueManager: QueueManager;\n eventManager?: EventManager;\n }> {\n // Connect to Redis\n const redisInstance = await this.redisManager.connect();\n\n // Connect to database\n const databaseInstance = this.databaseManager ? await this.databaseManager.connect() : null;\n\n let eventManager: EventManager | undefined;\n\n if (this.config.event?.enabled) {\n eventManager = new EventManager({\n applicationConfig: this.config,\n options: this.config.event,\n events: this.config.event.events || [],\n redisInstance,\n databaseInstance,\n // queueManager,\n });\n\n eventManager.load();\n }\n\n // Initialize queue\n const queueManager = new QueueManager({\n applicationConfig: this.config,\n options: {\n processorsDirectory: this.config.queue.processorsDirectory,\n },\n queues: this.config.queue.queues,\n redisInstance,\n databaseInstance,\n eventManager,\n });\n\n // Register queues\n await queueManager.registerQueues({\n queues: this.config.queue.queues,\n });\n\n return {\n redisInstance,\n databaseInstance,\n queueManager,\n eventManager,\n };\n }\n\n /**\n * Application started event\n */\n protected onStarted({ startupTime: _startupTime }: { startupTime: number }): void {}\n\n /**\n * Application stopped event\n */\n protected onStopped({ runtime: _runtime }: { runtime: number }): void {}\n\n /**\n * Before application stop event\n */\n private async onBeforeStop(): Promise<void> {\n // Disconnect from Redis\n await this.redisManager.disconnect();\n\n if (this.databaseManager) {\n // Disconnect from database\n await this.databaseManager.disconnect();\n }\n }\n\n /**\n * Start application instance\n */\n private async startInstance(options: ApplicationStartInstanceOptions): Promise<void> {\n try {\n // Before application start\n const { redisInstance, databaseInstance, queueManager, eventManager } = await this.onBeforeStart();\n\n // Start application\n await this.startHandler({\n redisInstance,\n databaseInstance,\n queueManager,\n eventManager,\n });\n\n // Calculate application startup time\n const startupTime = Time.calculateElapsedTime({\n startTime: this.startTime,\n });\n\n // On application started\n if (options.onStarted) {\n await options.onStarted({ startupTime });\n }\n } catch (error) {\n Logger.error({\n error: error instanceof Error ? error : new Error(String(error)),\n message: 'startInstance failure',\n });\n throw error;\n }\n }\n\n protected abstract startHandler({\n redisInstance,\n databaseInstance,\n queueManager,\n eventManager,\n }: {\n redisInstance: RedisInstance;\n databaseInstance?: DatabaseInstance | null;\n queueManager: QueueManager;\n eventManager?: EventManager | null;\n }): Promise<void>;\n\n protected abstract stopCallback(): void;\n\n /**\n * Set up global error handlers\n */\n /**\n * Initialize performance monitor\n */\n private initializePerformanceMonitor(): void {\n // Check if performance monitoring is enabled\n if (!this.config.performanceMonitoring?.enabled) {\n return;\n }\n\n // Initialize performance monitor with configuration\n this.performanceMonitor = PerformanceMonitor.initialize({\n enabled: true,\n thresholds: this.config.performanceMonitoring.thresholds,\n maxMetricsHistory: this.config.performanceMonitoring.maxMetricsHistory,\n logSlowOperations: this.config.performanceMonitoring.logSlowOperations,\n logAllOperations: this.config.performanceMonitoring.logAllOperations,\n });\n\n // Set up performance monitoring for different components\n if (this.config.performanceMonitoring.monitorDatabaseOperations !== false) {\n DatabasePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);\n }\n\n if (this.config.performanceMonitoring.monitorQueueOperations !== false) {\n QueuePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);\n }\n\n if (this.config.performanceMonitoring.monitorCacheOperations !== false) {\n CachePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);\n }\n\n // Set up periodic performance reports if configured\n if (this.config.performanceMonitoring?.reportInterval && this.config.performanceMonitoring.reportInterval > 0) {\n this.lifecycle.trackInterval(\n setInterval(() => {\n const reportFormat = this.config.performanceMonitoring?.reportFormat ?? 'detailed';\n const report = this.performanceMonitor?.generateFormattedReport(reportFormat);\n\n if (report) {\n Logger.info({ message: report });\n }\n }, this.config.performanceMonitoring.reportInterval),\n );\n }\n }\n\n private setupGlobalErrorHandlers(): void {\n // Handle uncaught exceptions\n process.on('uncaughtException', error => {\n Logger.error({ error, message: 'Uncaught Exception' });\n this.initiateGracefulShutdown();\n });\n\n // Handle unhandled promise rejections\n process.on('unhandledRejection', (reason, promise) => {\n Logger.error({\n error: reason instanceof Error ? reason : new Error(String(reason)),\n message: 'Unhandled Rejection',\n meta: { promise },\n });\n this.initiateGracefulShutdown();\n });\n }\n\n /**\n * Initiate graceful shutdown\n */\n private initiateGracefulShutdown(): void {\n if (this.isStopping) {\n return;\n }\n\n Logger.info({ message: 'Initiating graceful shutdown due to error' });\n this.stop().catch(error => {\n Logger.error({\n error: error instanceof Error ? error : new Error(String(error)),\n message: 'Error during graceful shutdown',\n });\n requestExit({ code: 1, reason: 'graceful-shutdown-error', error });\n });\n }\n\n /**\n * Handle shutdown\n */\n public handleShutdown({ onStopped }: { onStopped?: ({ runtime }: { runtime: number }) => void }): void {\n this.shutdownSignals.forEach(signal => {\n process.on(signal, async () => {\n // Stop application\n await this.stop({ onStopped });\n });\n });\n }\n\n /**\n * Stop application\n */\n private async stop({ onStopped }: ApplicationStopInstanceOptions = {}): Promise<void> {\n if (this.isStopping) {\n return;\n }\n\n this.isStopping = true;\n\n // Set timeout for forced termination\n const forceExitTimeout = setTimeout(() => {\n Logger.warn({ message: 'Forced shutdown due to timeout' });\n requestExit({ code: 1, reason: 'forced-timeout' });\n }, this.shutdownTimeout);\n\n try {\n // Stop callback\n await this.stopCallback();\n\n // Disconnect\n await this.onBeforeStop();\n\n if (onStopped) {\n // Calculate runtime\n const runtime = process.uptime() * 1000;\n // Emit stopped event\n await onStopped({ runtime });\n }\n\n clearTimeout(forceExitTimeout);\n requestExit({ code: 0, reason: 'shutdown-complete' });\n } catch (error) {\n Logger.error({\n error: error instanceof Error ? error : new Error(String(error)),\n message: 'Error during shutdown',\n });\n clearTimeout(forceExitTimeout);\n requestExit({ code: 1, reason: 'shutdown-error', error });\n }\n }\n}\n"],
|
|
5
|
-
"mappings": ";;AAAA,OAAO,aAAa;AACpB,SAAS,YAAY,oBAAoB;AACzC,SAAS,qBAAqB;AAC9B,SAAS,SAAS,MAAM,eAAe;AACvC,SAAgC,uBAAuB;AACvD,OAAO,kBAAkB;AACzB,OAAO,kBAAkB;AAMzB,OAAO,oBAAoB;AAE3B,SAAS,IAAI,YAAY;AACzB,OAAO,kBAAkB;AACzB,OAAO,QAAQ;AACf,OAAO,kBAAkB;AACzB,OAAO,YAAY;
|
|
4
|
+
"sourcesContent": ["import cluster from 'cluster';\nimport { existsSync, readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join, resolve } from 'path';\nimport { type DatabaseInstance, DatabaseManager } from '../database/index.js';\nimport QueueManager from '../queue/manager.js';\nimport RedisManager from '../redis/manager.js';\nimport type {\n ApplicationConfig,\n ApplicationStartInstanceOptions,\n ApplicationStopInstanceOptions,\n} from './base-application.interface.js';\nimport ClusterManager from '../cluster/cluster-manager.js';\nimport type RedisInstance from '../redis/instance.js';\nimport { OS, Time } from '../util/index.js';\nimport CacheManager from '../cache/manager.js';\nimport os from 'os';\nimport EventManager from '../event/manager.js';\nimport Logger from '../logger/logger.js';\nimport type { PerformanceMonitor } from '../performance/performance-monitor.js';\n// Performance monitoring now pluginized\nimport { PerformanceMonitorPlugin } from '../performance/performance-monitor.plugin.js';\nimport { type LifecycleConfig, LifecycleManager, ShutdownController } from '../lifecycle/index.js';\nimport { ConfigValidationError, formatConfigIssues, validateFrameworkConfig } from '../config/schema.js';\nimport { type ExitOutcome, requestExit } from '../lifecycle/exit.js';\n\n// Re-export types for external use\nexport type { ApplicationConfig } from './base-application.interface.js';\n\nexport default abstract class BaseApplication {\n /** Unique instance ID */\n public uniqueInstanceId: string;\n\n /** Application start time */\n protected startTime: number = 0;\n\n /** Shutdown timeout (30 seconds) */\n protected shutdownTimeout = 30000;\n\n /** Cache for application version to avoid repeated imports */\n private static applicationVersionCache: string | undefined;\n\n /** Cluster worker ID */\n protected workerId = cluster.isWorker && cluster.worker ? cluster.worker.id : null;\n\n /** Application config */\n protected config: ApplicationConfig;\n\n /** Application version */\n protected applicationVersion?: string;\n\n /** Redis manager */\n public redisManager: RedisManager;\n\n /** Cache manager */\n public cacheManager: CacheManager;\n\n /** Database manager */\n public databaseManager?: DatabaseManager;\n\n /** Queue manager */\n public queueManager?: QueueManager;\n\n /** Event manager */\n public eventManager?: EventManager;\n\n /** Performance monitor */\n public performanceMonitor?: PerformanceMonitor;\n\n /** Lifecycle manager */\n public lifecycle: LifecycleManager;\n\n /** Shutdown controller */\n public shutdownController: ShutdownController;\n\n public get Name() {\n return this.config.name;\n }\n\n /**\n * Application constructor\n */\n constructor(config: ApplicationConfig) {\n // Validate configuration early (fail-fast before side effects)\n try {\n const validated = validateFrameworkConfig(config as any);\n config = validated as unknown as ApplicationConfig;\n } catch (err) {\n if (err instanceof ConfigValidationError) {\n const formatted = formatConfigIssues(err.issues);\n throw new Error(`Configuration validation failed:\\n${formatted}`);\n }\n throw err;\n }\n const computerName = os.hostname();\n\n this.uniqueInstanceId = `${config.instanceId}-${computerName}-${OS.getUniqueComputerId()}`;\n this.config = config;\n\n // Initialize lifecycle management\n const lifecycleConfig: Partial<LifecycleConfig> = {\n gracefulShutdown: {\n timeoutMs: this.shutdownTimeout,\n },\n readiness: {\n timeoutMs: 30000,\n checkIntervalMs: 100,\n },\n };\n this.lifecycle = new LifecycleManager(lifecycleConfig);\n this.shutdownController = new ShutdownController(this.lifecycle);\n\n // Register shutdown hooks for cleanup\n this.registerShutdownHooks();\n\n // Initialize Redis manager\n this.redisManager = new RedisManager({\n applicationConfig: this.config,\n host: this.config.redis.host,\n port: this.config.redis.port,\n password: this.config.redis.password,\n });\n\n // Initialize cache manager\n this.cacheManager = new CacheManager({\n redisManager: this.redisManager,\n });\n\n // Register performance monitor plugin (idempotent & opt-in)\n PerformanceMonitorPlugin.register(this);\n\n // Set up global error handlers\n this.setupGlobalErrorHandlers();\n\n if (this.config.database && this.config.database.enabled === true) {\n const defaultEntitiesDirectory = join(this.config.rootDirectory, 'src', 'database', 'entities');\n\n if (!this.config.database.entitiesDirectory) {\n this.config.database.entitiesDirectory = defaultEntitiesDirectory;\n }\n\n if (!existsSync(this.config.database.entitiesDirectory)) {\n throw new Error(`Database entities directory not found (Path: ${this.config.database.entitiesDirectory})`);\n }\n\n // Initialize Database manager\n this.databaseManager = new DatabaseManager({\n applicationConfig: this.config,\n host: this.config.database.host,\n port: this.config.database.port,\n username: this.config.database.username,\n password: this.config.database.password,\n databaseName: this.config.database.databaseName,\n entitiesDirectory: this.config.database.entitiesDirectory,\n });\n }\n }\n\n /**\n * Get application version\n */\n public async getApplicationVersion(): Promise<string> {\n // Return cached version if available\n if (BaseApplication.applicationVersionCache !== undefined) {\n return BaseApplication.applicationVersionCache;\n }\n\n // Resolve the path to package.json\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const packageJsonPath = resolve(__dirname, '../../package.json');\n\n // Read and parse the file\n const fileContents = readFileSync(packageJsonPath, 'utf-8');\n const packageJson = JSON.parse(fileContents);\n\n if (!packageJson?.version) {\n throw new Error('Application version not found');\n }\n\n // Cache and return the version\n BaseApplication.applicationVersionCache = packageJson.version;\n\n return packageJson.version;\n }\n\n /**\n * Start application\n */\n public async start(): Promise<void> {\n // Start application timer\n this.startTime = Time.now();\n\n // Get application version`\n this.applicationVersion = await this.getApplicationVersion();\n\n const startInstanceOptions: ApplicationStartInstanceOptions = {\n onStarted: this.onStarted.bind(this),\n };\n\n const stopInstanceOptions: ApplicationStopInstanceOptions = {\n onStopped: this.onStopped.bind(this),\n };\n\n if (this.config.cluster?.enabled) {\n // Initialize clustered application\n const clusterManager = new ClusterManager({\n config: this.config.cluster,\n\n startApplicationCallback: () => this.startInstance(startInstanceOptions),\n stopApplicationCallback: () => this.stop(stopInstanceOptions),\n });\n\n // Start cluster\n clusterManager.start();\n } else {\n // Start standalone application\n await this.startInstance(startInstanceOptions);\n\n // Note: Signal handling should be implemented at the application launcher level\n // The lifecycle manager provides the stop() method for programmatic shutdown\n }\n }\n\n /**\n * Before application start\n */\n private async onBeforeStart(): Promise<{\n redisInstance: RedisInstance;\n databaseInstance: DatabaseInstance | null;\n queueManager: QueueManager;\n eventManager?: EventManager;\n }> {\n // Connect to Redis\n const redisInstance = await this.redisManager.connect();\n\n // Connect to database\n const databaseInstance = this.databaseManager ? await this.databaseManager.connect() : null;\n\n let eventManager: EventManager | undefined;\n\n if (this.config.event?.enabled) {\n eventManager = new EventManager({\n applicationConfig: this.config,\n options: this.config.event,\n events: this.config.event.events || [],\n redisInstance,\n databaseInstance,\n // queueManager,\n });\n\n eventManager.load();\n }\n\n // Initialize queue\n const queueManager = new QueueManager({\n applicationConfig: this.config,\n options: {\n processorsDirectory: this.config.queue.processorsDirectory,\n },\n queues: this.config.queue.queues,\n redisInstance,\n databaseInstance,\n eventManager,\n });\n\n // Register queues\n await queueManager.registerQueues({\n queues: this.config.queue.queues,\n });\n\n // Register readiness checks for key services\n this.lifecycle.addReadinessCheck('redis', async () => {\n try {\n return await redisInstance.isConnected();\n } catch {\n return false;\n }\n });\n\n if (databaseInstance) {\n this.lifecycle.addReadinessCheck('database', async () => {\n try {\n return await databaseInstance.isConnected();\n } catch {\n return false;\n }\n });\n }\n\n return {\n redisInstance,\n databaseInstance,\n queueManager,\n eventManager,\n };\n }\n\n /**\n * Application started event\n */\n protected onStarted({ startupTime: _startupTime }: { startupTime: number }): void {}\n\n /**\n * Application stopped event\n */\n protected onStopped({ runtime: _runtime }: { runtime: number }): void {}\n\n /**\n * Start application instance\n */\n private async startInstance(options: ApplicationStartInstanceOptions): Promise<void> {\n try {\n // Phase 1: Initialize (resource setup)\n const initResult = await this.lifecycle.initialize();\n if (initResult.errors.length > 0) {\n Logger.warn({\n message: 'Lifecycle init phase encountered errors',\n meta: { errors: initResult.errors.map(e => (e instanceof Error ? e.message : String(e))) },\n });\n }\n\n // Before application start\n const { redisInstance, databaseInstance, queueManager, eventManager } = await this.onBeforeStart();\n\n // Phase 2: Start (component startup)\n const startResult = await this.lifecycle.start();\n if (startResult.errors.length > 0) {\n Logger.warn({\n message: 'Lifecycle start phase encountered errors',\n meta: { errors: startResult.errors.map(e => (e instanceof Error ? e.message : String(e))) },\n });\n }\n\n // Start application\n await this.startHandler({\n redisInstance,\n databaseInstance,\n queueManager,\n eventManager,\n });\n\n // Phase 3: Ready (application accepting traffic)\n const readyResult = await this.lifecycle.ready();\n if (readyResult.errors.length > 0) {\n Logger.warn({\n message: 'Lifecycle ready phase encountered errors',\n meta: { errors: readyResult.errors.map(e => (e instanceof Error ? e.message : String(e))) },\n });\n }\n\n // Calculate application startup time\n const startupTime = Time.calculateElapsedTimeMs({\n startTime: this.startTime,\n });\n\n // On application started\n if (options.onStarted) {\n await options.onStarted({ startupTime });\n }\n } catch (error) {\n Logger.error({\n error: error instanceof Error ? error : new Error(String(error)),\n message: 'startInstance failure',\n });\n throw error;\n }\n }\n\n protected abstract startHandler({\n redisInstance,\n databaseInstance,\n queueManager,\n eventManager,\n }: {\n redisInstance: RedisInstance;\n databaseInstance?: DatabaseInstance | null;\n queueManager: QueueManager;\n eventManager?: EventManager | null;\n }): Promise<void>;\n\n protected abstract stopCallback(): void;\n\n /**\n * Set up global error handlers\n */\n /**\n * Initialize performance monitor\n */\n // initializePerformanceMonitor deprecated in favor of PerformanceMonitorPlugin\n // (left intentionally absent)\n\n private setupGlobalErrorHandlers(): void {\n // Handle uncaught exceptions\n process.on('uncaughtException', error => {\n Logger.error({ error, message: 'Uncaught Exception' });\n this.initiateGracefulShutdown();\n });\n\n // Handle unhandled promise rejections\n process.on('unhandledRejection', (reason, promise) => {\n Logger.error({\n error: reason instanceof Error ? reason : new Error(String(reason)),\n message: 'Unhandled Rejection',\n meta: { promise },\n });\n this.initiateGracefulShutdown();\n });\n }\n\n /**\n * Register shutdown hooks for proper cleanup\n */\n private registerShutdownHooks(): void {\n // Register shutdown hooks in reverse dependency order\n this.lifecycle.onShutdown(async () => {\n Logger.info({ message: 'Executing custom stop callback' });\n await this.stopCallback();\n });\n\n this.lifecycle.onShutdown(async () => {\n if (this.redisManager) {\n Logger.info({ message: 'Disconnecting from Redis' });\n await this.redisManager.disconnect();\n }\n });\n\n this.lifecycle.onShutdown(async () => {\n if (this.databaseManager) {\n Logger.info({ message: 'Disconnecting from database' });\n await this.databaseManager.disconnect();\n }\n });\n\n // Performance monitor is handled via trackInterval, so it will be cleaned up automatically\n }\n\n /**\n * Initiate graceful shutdown\n */\n private async initiateGracefulShutdown(): Promise<void> {\n if (this.shutdownController.isShuttingDown) {\n return;\n }\n\n Logger.info({ message: 'Initiating graceful shutdown due to error' });\n try {\n const result = await this.shutdownController.initiate('error-triggered');\n if (result.errors.length > 0) {\n Logger.error({\n message: 'Errors during shutdown',\n error: result.errors,\n });\n this.finalizeExit({ code: 1, reason: 'graceful-shutdown-error', error: result.errors });\n } else if (result.timedOut) {\n Logger.warn({ message: 'Shutdown timed out' });\n this.finalizeExit({ code: 1, reason: 'shutdown-timeout' });\n } else {\n this.finalizeExit({ code: 0, reason: 'error-shutdown-complete' });\n }\n } catch (error) {\n Logger.error({\n error: error instanceof Error ? error : new Error(String(error)),\n message: 'Error during graceful shutdown',\n });\n this.finalizeExit({ code: 1, reason: 'graceful-shutdown-error', error });\n }\n }\n\n /**\n * Stop application using lifecycle manager\n */\n public async stop({ onStopped }: ApplicationStopInstanceOptions = {}): Promise<void> {\n if (this.shutdownController.isShuttingDown) {\n return;\n }\n\n // Register the onStopped callback if provided\n if (onStopped) {\n this.lifecycle.onShutdown(() => {\n const runtime = process.uptime() * 1000;\n onStopped({ runtime });\n });\n }\n\n try {\n const result = await this.shutdownController.initiate('manual-stop');\n if (result.errors.length > 0) {\n Logger.error({\n message: 'Errors during shutdown',\n error: result.errors,\n });\n this.finalizeExit({ code: 1, reason: 'shutdown-error', error: result.errors });\n } else if (result.timedOut) {\n Logger.warn({ message: 'Shutdown timed out' });\n this.finalizeExit({ code: 1, reason: 'shutdown-timeout' });\n } else {\n this.finalizeExit({ code: 0, reason: 'shutdown-complete' });\n }\n } catch (error) {\n Logger.error({\n error: error instanceof Error ? error : new Error(String(error)),\n message: 'Error during shutdown',\n });\n this.finalizeExit({ code: 1, reason: 'shutdown-error', error });\n }\n }\n\n /**\n * Finalize exit: during tests, suppress actual process exit to avoid failing vitest runs.\n */\n private finalizeExit(outcome: ExitOutcome): void {\n const nodeEnv = process.env.NODE_ENV ?? '';\n const isTestEnv =\n nodeEnv.toLowerCase() === 'test' ||\n 'VITEST' in process.env ||\n 'VITEST_WORKER_ID' in process.env ||\n process.argv.some(a => a.includes('vitest')) ||\n typeof (globalThis as any).afterAll === 'function';\n\n if (isTestEnv) {\n Logger.info({ message: `Skipping process exit in test environment (${outcome.reason})`, code: outcome.code });\n return;\n }\n requestExit(outcome);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-application.d.ts","sourceRoot":"","sources":["../../src/application/command-application.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command-application.d.ts","sourceRoot":"","sources":["../../src/application/command-application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAE5D,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,eAAe,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAGnF,MAAM,CAAC,OAAO,OAAO,kBAAmB,SAAQ,eAAe;IAC7D,iCAAiC;IACjC,SAAS,CAAC,MAAM,EAAE,wBAAwB,CAAC;gBAE/B,MAAM,EAAE,wBAAwB;cA0B5B,YAAY,CAAC,EAC3B,aAAa,EACb,gBAAgB,EAChB,YAAY,GACb,EAAE;QACD,aAAa,EAAE,aAAa,CAAC;QAC7B,gBAAgB,EAAE,gBAAgB,CAAC;QACnC,YAAY,EAAE,YAAY,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IA4FjB,OAAO,CAAC,WAAW;IAKnB,SAAS,CAAC,YAAY,IAAI,IAAI;CAG/B"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
3
|
import { Logger } from "../logger/index.js";
|
|
5
4
|
import BaseApplication from "./base-application.js";
|
|
6
|
-
import { Helper, Loader, Time } from "../util/index.js";
|
|
5
|
+
import { File, Helper, Loader, Time } from "../util/index.js";
|
|
7
6
|
class CommandApplication extends BaseApplication {
|
|
8
7
|
static {
|
|
9
8
|
__name(this, "CommandApplication");
|
|
@@ -43,7 +42,7 @@ class CommandApplication extends BaseApplication {
|
|
|
43
42
|
return;
|
|
44
43
|
}
|
|
45
44
|
const inputCommandName = parsedArgv._[0];
|
|
46
|
-
const commandsDirectoryExists = await
|
|
45
|
+
const commandsDirectoryExists = await File.pathExists(this.config.commandsDirectory);
|
|
47
46
|
if (!commandsDirectoryExists) {
|
|
48
47
|
Logger.warn({
|
|
49
48
|
message: "Commands directory not found",
|
|
@@ -98,7 +97,7 @@ class CommandApplication extends BaseApplication {
|
|
|
98
97
|
this.stopCommand();
|
|
99
98
|
}
|
|
100
99
|
stopCommand() {
|
|
101
|
-
this.
|
|
100
|
+
void this.stop({ onStopped: this.onStopped.bind(this) });
|
|
102
101
|
}
|
|
103
102
|
stopCallback() {
|
|
104
103
|
Logger.info({ message: "Command stopped" });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/application/command-application.ts"],
|
|
4
|
-
"sourcesContent": ["import
|
|
5
|
-
"mappings": ";;
|
|
4
|
+
"sourcesContent": ["import type DatabaseInstance from '../database/instance.js';\nimport { Logger } from '../logger/index.js';\nimport type QueueManager from '../queue/manager.js';\nimport type RedisInstance from '../redis/instance.js';\nimport BaseApplication from './base-application.js';\nimport type { CommandApplicationConfig } from './command-application.interface.js';\nimport { File, Helper, Loader, Time } from '../util/index.js';\n\nexport default class CommandApplication extends BaseApplication {\n /** Command application config */\n protected config: CommandApplicationConfig;\n\n constructor(config: CommandApplicationConfig) {\n super(config);\n\n const defaultConfig: Partial<CommandApplicationConfig> = {\n cluster: {\n enabled: false,\n },\n\n log: {\n startUp: false,\n },\n\n debug: {\n measureExecutionTime: false,\n },\n };\n\n const mergedConfig: CommandApplicationConfig = Helper.defaultsDeep(config, defaultConfig);\n\n if (mergedConfig.cluster) {\n mergedConfig.cluster.enabled = false;\n }\n\n this.config = mergedConfig;\n }\n\n protected async startHandler({\n redisInstance,\n databaseInstance,\n queueManager,\n }: {\n redisInstance: RedisInstance;\n databaseInstance: DatabaseInstance;\n queueManager: QueueManager;\n }): Promise<void> {\n const startTime = performance.now();\n\n // get argv (yargs) input args\n const argv = this.config.commandManager.argv;\n\n const parsedArgv = argv.parseSync();\n\n if (parsedArgv._.length === 0) {\n Logger.warn({ message: 'No command provided' });\n\n this.stopCommand();\n\n return;\n }\n\n const inputCommandName = parsedArgv._[0];\n\n const commandsDirectoryExists = await File.pathExists(this.config.commandsDirectory);\n\n if (!commandsDirectoryExists) {\n Logger.warn({\n message: 'Commands directory not found',\n meta: {\n Directory: this.config.commandsDirectory,\n },\n });\n\n return;\n }\n\n // Load commands\n const commands = await Loader.loadModulesInDirectory({\n directory: this.config.commandsDirectory,\n extensions: ['.ts', '.js'],\n });\n\n // Find command by name - use safe property access to prevent object injection\n let CommandClass = null;\n if (Object.prototype.hasOwnProperty.call(commands, inputCommandName)) {\n CommandClass = Reflect.get(commands, inputCommandName);\n }\n\n if (!CommandClass) {\n Logger.warn({\n message: 'Command not found',\n meta: { Command: inputCommandName },\n });\n\n return;\n }\n\n // Initialize command\n const command = new CommandClass({\n applicationConfig: this.config,\n redisInstance,\n queueManager,\n databaseInstance,\n });\n\n Logger.info({\n message: 'Command started',\n meta: { Command: inputCommandName },\n });\n\n // Run command\n await command.run(parsedArgv);\n\n const commandCompletedLogParams: Record<string, unknown> = {\n Command: inputCommandName,\n };\n\n if (this.config.debug?.measureExecutionTime) {\n const endTime = performance.now();\n const executionTime = endTime - startTime;\n\n commandCompletedLogParams['Execution Time'] = Time.formatTime({\n time: executionTime,\n numDecimals: 2,\n showUnit: true,\n });\n }\n\n Logger.info({\n message: 'Command completed',\n meta: commandCompletedLogParams,\n });\n\n // Call shutdown signtal to stop the command\n this.stopCommand();\n }\n\n private stopCommand(): void {\n // Use modern lifecycle management for graceful shutdown\n void this.stop({ onStopped: this.onStopped.bind(this) });\n }\n\n protected stopCallback(): void {\n Logger.info({ message: 'Command stopped' });\n }\n}\n"],
|
|
5
|
+
"mappings": ";;AACA,SAAS,cAAc;AAGvB,OAAO,qBAAqB;AAE5B,SAAS,MAAM,QAAQ,QAAQ,YAAY;AAE3C,MAAO,2BAAyC,gBAAgB;AAAA,EARhE,OAQgE;AAAA;AAAA;AAAA;AAAA,EAEpD;AAAA,EAEV,YAAY,QAAkC;AAC5C,UAAM,MAAM;AAEZ,UAAM,gBAAmD;AAAA,MACvD,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MAEA,KAAK;AAAA,QACH,SAAS;AAAA,MACX;AAAA,MAEA,OAAO;AAAA,QACL,sBAAsB;AAAA,MACxB;AAAA,IACF;AAEA,UAAM,eAAyC,OAAO,aAAa,QAAQ,aAAa;AAExF,QAAI,aAAa,SAAS;AACxB,mBAAa,QAAQ,UAAU;AAAA,IACjC;AAEA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAgB,aAAa;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIkB;AAChB,UAAM,YAAY,YAAY,IAAI;AAGlC,UAAM,OAAO,KAAK,OAAO,eAAe;AAExC,UAAM,aAAa,KAAK,UAAU;AAElC,QAAI,WAAW,EAAE,WAAW,GAAG;AAC7B,aAAO,KAAK,EAAE,SAAS,sBAAsB,CAAC;AAE9C,WAAK,YAAY;AAEjB;AAAA,IACF;AAEA,UAAM,mBAAmB,WAAW,EAAE,CAAC;AAEvC,UAAM,0BAA0B,MAAM,KAAK,WAAW,KAAK,OAAO,iBAAiB;AAEnF,QAAI,CAAC,yBAAyB;AAC5B,aAAO,KAAK;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,WAAW,KAAK,OAAO;AAAA,QACzB;AAAA,MACF,CAAC;AAED;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,OAAO,uBAAuB;AAAA,MACnD,WAAW,KAAK,OAAO;AAAA,MACvB,YAAY,CAAC,OAAO,KAAK;AAAA,IAC3B,CAAC;AAGD,QAAI,eAAe;AACnB,QAAI,OAAO,UAAU,eAAe,KAAK,UAAU,gBAAgB,GAAG;AACpE,qBAAe,QAAQ,IAAI,UAAU,gBAAgB;AAAA,IACvD;AAEA,QAAI,CAAC,cAAc;AACjB,aAAO,KAAK;AAAA,QACV,SAAS;AAAA,QACT,MAAM,EAAE,SAAS,iBAAiB;AAAA,MACpC,CAAC;AAED;AAAA,IACF;AAGA,UAAM,UAAU,IAAI,aAAa;AAAA,MAC/B,mBAAmB,KAAK;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,KAAK;AAAA,MACV,SAAS;AAAA,MACT,MAAM,EAAE,SAAS,iBAAiB;AAAA,IACpC,CAAC;AAGD,UAAM,QAAQ,IAAI,UAAU;AAE5B,UAAM,4BAAqD;AAAA,MACzD,SAAS;AAAA,IACX;AAEA,QAAI,KAAK,OAAO,OAAO,sBAAsB;AAC3C,YAAM,UAAU,YAAY,IAAI;AAChC,YAAM,gBAAgB,UAAU;AAEhC,gCAA0B,gBAAgB,IAAI,KAAK,WAAW;AAAA,QAC5D,MAAM;AAAA,QACN,aAAa;AAAA,QACb,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,MACV,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAGD,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,cAAoB;AAE1B,SAAK,KAAK,KAAK,EAAE,WAAW,KAAK,UAAU,KAAK,IAAI,EAAE,CAAC;AAAA,EACzD;AAAA,EAEU,eAAqB;AAC7B,WAAO,KAAK,EAAE,SAAS,kBAAkB,CAAC;AAAA,EAC5C;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-application.d.ts","sourceRoot":"","sources":["../../src/application/web-application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,SAAS,MAAM,2BAA2B,CAAC;AAClD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,eAAe,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E,OAAO,eAAe,MAAM,kCAAkC,CAAC;AAC/D,OAAO,eAAe,MAAM,kCAAkC,CAAC;AAC/D,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AAGpD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,eAAe;IACzD,6BAA6B;IAC7B,SAAS,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAEvC,iBAAiB;IACV,SAAS,CAAC,EAAE,SAAS,CAAC;IAE7B,uBAAuB;IAChB,eAAe,CAAC,EAAE,eAAe,CAAC;IAEzC,uBAAuB;IAChB,eAAe,CAAC,EAAE,eAAe,CAAC;gBAE7B,MAAM,EAAE,oBAAoB;cAcxB,YAAY,CAAC,EAC3B,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,YAAY,GACb,EAAE;QACD,aAAa,EAAE,aAAa,CAAC;QAC7B,gBAAgB,EAAE,gBAAgB,CAAC;QACnC,YAAY,EAAE,YAAY,CAAC;QAC3B,YAAY,EAAE,YAAY,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"web-application.d.ts","sourceRoot":"","sources":["../../src/application/web-application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,SAAS,MAAM,2BAA2B,CAAC;AAClD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,eAAe,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E,OAAO,eAAe,MAAM,kCAAkC,CAAC;AAC/D,OAAO,eAAe,MAAM,kCAAkC,CAAC;AAC/D,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AAGpD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,eAAe;IACzD,6BAA6B;IAC7B,SAAS,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAEvC,iBAAiB;IACV,SAAS,CAAC,EAAE,SAAS,CAAC;IAE7B,uBAAuB;IAChB,eAAe,CAAC,EAAE,eAAe,CAAC;IAEzC,uBAAuB;IAChB,eAAe,CAAC,EAAE,eAAe,CAAC;gBAE7B,MAAM,EAAE,oBAAoB;cAcxB,YAAY,CAAC,EAC3B,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,YAAY,GACb,EAAE;QACD,aAAa,EAAE,aAAa,CAAC;QAC7B,gBAAgB,EAAE,gBAAgB,CAAC;QACnC,YAAY,EAAE,YAAY,CAAC;QAC3B,YAAY,EAAE,YAAY,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IAkHjB;;OAEG;cACa,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAY7C;;OAEG;cACa,SAAS,CAAC,EAAE,WAAW,EAAE,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;cA0BlE,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAqB3E"}
|
|
@@ -38,7 +38,6 @@ class WebApplication extends BaseApplication {
|
|
|
38
38
|
if (this.config.webServer?.enabled) {
|
|
39
39
|
this.webServer = new WebServer({
|
|
40
40
|
applicationConfig: this.config,
|
|
41
|
-
// config: this.config.webServer,
|
|
42
41
|
options: {
|
|
43
42
|
host: this.config.webServer.host,
|
|
44
43
|
port: this.config.webServer.port,
|
|
@@ -51,13 +50,21 @@ class WebApplication extends BaseApplication {
|
|
|
51
50
|
redisInstance,
|
|
52
51
|
databaseInstance,
|
|
53
52
|
queueManager,
|
|
54
|
-
eventManager
|
|
53
|
+
eventManager,
|
|
54
|
+
lifecycleManager: this.lifecycle
|
|
55
55
|
});
|
|
56
56
|
if (this.performanceMonitor && this.config.performanceMonitoring?.monitorHttpRequests !== false) {
|
|
57
57
|
WebServerPerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
58
58
|
}
|
|
59
59
|
await this.webServer.load();
|
|
60
60
|
await this.webServer.start();
|
|
61
|
+
this.lifecycle.addReadinessCheck("webserver", async () => {
|
|
62
|
+
try {
|
|
63
|
+
return this.webServer?.isReady() ?? false;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
if (this.config.webSocket?.enabled) {
|
|
63
70
|
if (!this.webServer) {
|