@scpxl/nodejs-framework 1.0.14 → 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 +16 -20
- package/dist/application/base-application.d.ts.map +1 -1
- package/dist/application/base-application.js +152 -109
- 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 +7 -9
- 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 +11 -0
- package/dist/lifecycle/exit.d.ts.map +1 -0
- package/dist/lifecycle/exit.js +29 -0
- package/dist/lifecycle/exit.js.map +7 -0
- 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 +66 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
- package/dist/lifecycle/lifecycle-manager.js +280 -0
- package/dist/lifecycle/lifecycle-manager.js.map +7 -0
- 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 +68 -25
|
@@ -13,20 +13,18 @@ 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 {
|
|
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
|
+
import { requestExit } from "../lifecycle/exit.js";
|
|
18
20
|
class BaseApplication {
|
|
19
21
|
static {
|
|
20
22
|
__name(this, "BaseApplication");
|
|
21
23
|
}
|
|
22
24
|
/** Unique instance ID */
|
|
23
25
|
uniqueInstanceId;
|
|
24
|
-
/** Shutdown signals */
|
|
25
|
-
shutdownSignals = ["SIGTERM", "SIGINT"];
|
|
26
26
|
/** Application start time */
|
|
27
|
-
startTime =
|
|
28
|
-
/** Whether application is stopping */
|
|
29
|
-
isStopping = false;
|
|
27
|
+
startTime = 0;
|
|
30
28
|
/** Shutdown timeout (30 seconds) */
|
|
31
29
|
shutdownTimeout = 3e4;
|
|
32
30
|
/** Cache for application version to avoid repeated imports */
|
|
@@ -49,6 +47,10 @@ class BaseApplication {
|
|
|
49
47
|
eventManager;
|
|
50
48
|
/** Performance monitor */
|
|
51
49
|
performanceMonitor;
|
|
50
|
+
/** Lifecycle manager */
|
|
51
|
+
lifecycle;
|
|
52
|
+
/** Shutdown controller */
|
|
53
|
+
shutdownController;
|
|
52
54
|
get Name() {
|
|
53
55
|
return this.config.name;
|
|
54
56
|
}
|
|
@@ -56,9 +58,32 @@ class BaseApplication {
|
|
|
56
58
|
* Application constructor
|
|
57
59
|
*/
|
|
58
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
|
+
}
|
|
59
72
|
const computerName = os.hostname();
|
|
60
73
|
this.uniqueInstanceId = `${config.instanceId}-${computerName}-${OS.getUniqueComputerId()}`;
|
|
61
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();
|
|
62
87
|
this.redisManager = new RedisManager({
|
|
63
88
|
applicationConfig: this.config,
|
|
64
89
|
host: this.config.redis.host,
|
|
@@ -66,10 +91,9 @@ class BaseApplication {
|
|
|
66
91
|
password: this.config.redis.password
|
|
67
92
|
});
|
|
68
93
|
this.cacheManager = new CacheManager({
|
|
69
|
-
applicationConfig: this.config,
|
|
70
94
|
redisManager: this.redisManager
|
|
71
95
|
});
|
|
72
|
-
|
|
96
|
+
PerformanceMonitorPlugin.register(this);
|
|
73
97
|
this.setupGlobalErrorHandlers();
|
|
74
98
|
if (this.config.database && this.config.database.enabled === true) {
|
|
75
99
|
const defaultEntitiesDirectory = join(this.config.rootDirectory, "src", "database", "entities");
|
|
@@ -112,35 +136,12 @@ class BaseApplication {
|
|
|
112
136
|
* Start application
|
|
113
137
|
*/
|
|
114
138
|
async start() {
|
|
115
|
-
this.startTime =
|
|
139
|
+
this.startTime = Time.now();
|
|
116
140
|
this.applicationVersion = await this.getApplicationVersion();
|
|
117
141
|
const startInstanceOptions = {
|
|
118
|
-
// onStarted: ({ startupTime }) => {
|
|
119
|
-
// if (this.config.log?.startUp) {
|
|
120
|
-
// Logger.info('Application started', {
|
|
121
|
-
// Name: this.config.name,
|
|
122
|
-
// 'PXL Framework Version': this.applicationVersion,
|
|
123
|
-
// 'Startup Time': Time.formatTime({ time: startupTime, format: 's', numDecimals: 2, showUnit: true }),
|
|
124
|
-
// });
|
|
125
|
-
// }
|
|
126
|
-
// if (this.config.events?.onStarted) {
|
|
127
|
-
// this.config.events.onStarted({ app: this, startupTime });
|
|
128
|
-
// }
|
|
129
|
-
// },
|
|
130
142
|
onStarted: this.onStarted.bind(this)
|
|
131
143
|
};
|
|
132
144
|
const stopInstanceOptions = {
|
|
133
|
-
// onStopped: ({ runtime }) => {
|
|
134
|
-
// if (this.config.log?.shutdown) {
|
|
135
|
-
// Logger.info('Application stopped', {
|
|
136
|
-
// Name: this.config.name,
|
|
137
|
-
// 'Runtime': Time.formatTime({ time: runtime, format: 's', numDecimals: 2, showUnit: true }),
|
|
138
|
-
// });
|
|
139
|
-
// }
|
|
140
|
-
// if (this.config.events?.onStopped) {
|
|
141
|
-
// this.config.events.onStopped({ app: this, runtime });
|
|
142
|
-
// }
|
|
143
|
-
// },
|
|
144
145
|
onStopped: this.onStopped.bind(this)
|
|
145
146
|
};
|
|
146
147
|
if (this.config.cluster?.enabled) {
|
|
@@ -152,9 +153,6 @@ class BaseApplication {
|
|
|
152
153
|
clusterManager.start();
|
|
153
154
|
} else {
|
|
154
155
|
await this.startInstance(startInstanceOptions);
|
|
155
|
-
this.handleShutdown({
|
|
156
|
-
onStopped: stopInstanceOptions.onStopped
|
|
157
|
-
});
|
|
158
156
|
}
|
|
159
157
|
}
|
|
160
158
|
/**
|
|
@@ -188,6 +186,22 @@ class BaseApplication {
|
|
|
188
186
|
await queueManager.registerQueues({
|
|
189
187
|
queues: this.config.queue.queues
|
|
190
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
|
+
}
|
|
191
205
|
return {
|
|
192
206
|
redisInstance,
|
|
193
207
|
databaseInstance,
|
|
@@ -205,36 +219,51 @@ class BaseApplication {
|
|
|
205
219
|
*/
|
|
206
220
|
onStopped({ runtime: _runtime }) {
|
|
207
221
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Before application stop event
|
|
210
|
-
*/
|
|
211
|
-
async onBeforeStop() {
|
|
212
|
-
await this.redisManager.disconnect();
|
|
213
|
-
if (this.databaseManager) {
|
|
214
|
-
await this.databaseManager.disconnect();
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
222
|
/**
|
|
218
223
|
* Start application instance
|
|
219
224
|
*/
|
|
220
225
|
async startInstance(options) {
|
|
221
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
|
+
}
|
|
222
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
|
+
}
|
|
223
242
|
await this.startHandler({
|
|
224
243
|
redisInstance,
|
|
225
244
|
databaseInstance,
|
|
226
245
|
queueManager,
|
|
227
246
|
eventManager
|
|
228
247
|
});
|
|
229
|
-
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({
|
|
230
256
|
startTime: this.startTime
|
|
231
257
|
});
|
|
232
258
|
if (options.onStarted) {
|
|
233
259
|
await options.onStarted({ startupTime });
|
|
234
260
|
}
|
|
235
261
|
} catch (error) {
|
|
236
|
-
|
|
237
|
-
|
|
262
|
+
Logger.error({
|
|
263
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
264
|
+
message: "startInstance failure"
|
|
265
|
+
});
|
|
266
|
+
throw error;
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
/**
|
|
@@ -243,36 +272,8 @@ class BaseApplication {
|
|
|
243
272
|
/**
|
|
244
273
|
* Initialize performance monitor
|
|
245
274
|
*/
|
|
246
|
-
initializePerformanceMonitor
|
|
247
|
-
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
this.performanceMonitor = PerformanceMonitor.initialize({
|
|
251
|
-
enabled: true,
|
|
252
|
-
thresholds: this.config.performanceMonitoring.thresholds,
|
|
253
|
-
maxMetricsHistory: this.config.performanceMonitoring.maxMetricsHistory,
|
|
254
|
-
logSlowOperations: this.config.performanceMonitoring.logSlowOperations,
|
|
255
|
-
logAllOperations: this.config.performanceMonitoring.logAllOperations
|
|
256
|
-
});
|
|
257
|
-
if (this.config.performanceMonitoring.monitorDatabaseOperations !== false) {
|
|
258
|
-
DatabasePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
259
|
-
}
|
|
260
|
-
if (this.config.performanceMonitoring.monitorQueueOperations !== false) {
|
|
261
|
-
QueuePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
262
|
-
}
|
|
263
|
-
if (this.config.performanceMonitoring.monitorCacheOperations !== false) {
|
|
264
|
-
CachePerformanceWrapper.setPerformanceMonitor(this.performanceMonitor);
|
|
265
|
-
}
|
|
266
|
-
if (this.config.performanceMonitoring.reportInterval && this.config.performanceMonitoring.reportInterval > 0) {
|
|
267
|
-
setInterval(() => {
|
|
268
|
-
const reportFormat = this.config.performanceMonitoring?.reportFormat ?? "detailed";
|
|
269
|
-
const report = this.performanceMonitor?.generateFormattedReport(reportFormat);
|
|
270
|
-
if (report) {
|
|
271
|
-
Logger.info({ message: report });
|
|
272
|
-
}
|
|
273
|
-
}, this.config.performanceMonitoring.reportInterval);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
275
|
+
// initializePerformanceMonitor deprecated in favor of PerformanceMonitorPlugin
|
|
276
|
+
// (left intentionally absent)
|
|
276
277
|
setupGlobalErrorHandlers() {
|
|
277
278
|
process.on("uncaughtException", (error) => {
|
|
278
279
|
Logger.error({ error, message: "Uncaught Exception" });
|
|
@@ -287,61 +288,103 @@ class BaseApplication {
|
|
|
287
288
|
this.initiateGracefulShutdown();
|
|
288
289
|
});
|
|
289
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
|
+
}
|
|
290
312
|
/**
|
|
291
313
|
* Initiate graceful shutdown
|
|
292
314
|
*/
|
|
293
|
-
initiateGracefulShutdown() {
|
|
294
|
-
if (this.
|
|
315
|
+
async initiateGracefulShutdown() {
|
|
316
|
+
if (this.shutdownController.isShuttingDown) {
|
|
295
317
|
return;
|
|
296
318
|
}
|
|
297
319
|
Logger.info({ message: "Initiating graceful shutdown due to error" });
|
|
298
|
-
|
|
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) {
|
|
299
335
|
Logger.error({
|
|
300
336
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
301
337
|
message: "Error during graceful shutdown"
|
|
302
338
|
});
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Handle shutdown
|
|
308
|
-
*/
|
|
309
|
-
handleShutdown({ onStopped }) {
|
|
310
|
-
this.shutdownSignals.forEach((signal) => {
|
|
311
|
-
process.on(signal, async () => {
|
|
312
|
-
await this.stop({ onStopped });
|
|
313
|
-
});
|
|
314
|
-
});
|
|
339
|
+
this.finalizeExit({ code: 1, reason: "graceful-shutdown-error", error });
|
|
340
|
+
}
|
|
315
341
|
}
|
|
316
342
|
/**
|
|
317
|
-
* Stop application
|
|
343
|
+
* Stop application using lifecycle manager
|
|
318
344
|
*/
|
|
319
345
|
async stop({ onStopped } = {}) {
|
|
320
|
-
if (this.
|
|
346
|
+
if (this.shutdownController.isShuttingDown) {
|
|
321
347
|
return;
|
|
322
348
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
Logger.warn({ message: "Forced shutdown due to timeout" });
|
|
326
|
-
process.exit(1);
|
|
327
|
-
}, this.shutdownTimeout);
|
|
328
|
-
try {
|
|
329
|
-
await this.stopCallback();
|
|
330
|
-
await this.onBeforeStop();
|
|
331
|
-
if (onStopped) {
|
|
349
|
+
if (onStopped) {
|
|
350
|
+
this.lifecycle.onShutdown(() => {
|
|
332
351
|
const runtime = process.uptime() * 1e3;
|
|
333
|
-
|
|
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" });
|
|
334
368
|
}
|
|
335
|
-
clearTimeout(forceExitTimeout);
|
|
336
|
-
process.exit(0);
|
|
337
369
|
} catch (error) {
|
|
338
370
|
Logger.error({
|
|
339
371
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
340
372
|
message: "Error during shutdown"
|
|
341
373
|
});
|
|
342
|
-
|
|
343
|
-
|
|
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;
|
|
344
386
|
}
|
|
387
|
+
requestExit(outcome);
|
|
345
388
|
}
|
|
346
389
|
}
|
|
347
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';\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 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 // Log error\n console.error(error);\n\n process.exit(1);\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 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 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 process.exit(1);\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 process.exit(1);\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\n // Emit stopped event\n await onStopped({ runtime });\n }\n\n clearTimeout(forceExitTimeout);\n process.exit(0);\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 process.exit(1);\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) {
|