@catbee/utils 2.2.0 → 2.2.1
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/healthz-server/index.cjs +22 -9
- package/healthz-server/index.d.ts +16 -8
- package/healthz-server/index.mjs +22 -9
- package/package.json +1 -1
- package/server/index.cjs +107 -79
- package/server/index.d.ts +10 -0
- package/server/index.mjs +107 -79
package/healthz-server/index.cjs
CHANGED
|
@@ -67,8 +67,10 @@ var HealthzServer = class _HealthzServer {
|
|
|
67
67
|
server;
|
|
68
68
|
config;
|
|
69
69
|
startedAt = Date.now();
|
|
70
|
-
/** Whether the health server
|
|
71
|
-
|
|
70
|
+
/** Whether the health HTTP server is currently running and listening on its port */
|
|
71
|
+
running = false;
|
|
72
|
+
/** Whether application startup has completed (for `/startupz`) */
|
|
73
|
+
startupComplete = false;
|
|
72
74
|
/** Whether the service is ready to receive traffic (for `/readyz`) */
|
|
73
75
|
ready = false;
|
|
74
76
|
shuttingDown = false;
|
|
@@ -109,7 +111,7 @@ var HealthzServer = class _HealthzServer {
|
|
|
109
111
|
port: config.port
|
|
110
112
|
}, () => {
|
|
111
113
|
instance.server.off("error", onError);
|
|
112
|
-
instance.
|
|
114
|
+
instance.running = true;
|
|
113
115
|
const addr = instance.server.address();
|
|
114
116
|
if (!addr || typeof addr === "string") {
|
|
115
117
|
instance.cleanup();
|
|
@@ -125,9 +127,19 @@ var HealthzServer = class _HealthzServer {
|
|
|
125
127
|
});
|
|
126
128
|
});
|
|
127
129
|
}
|
|
128
|
-
/** Whether the
|
|
129
|
-
static
|
|
130
|
-
return _global[SINGLETON_KEY]?.
|
|
130
|
+
/** Whether the Healthz HTTP probe server is currently running and listening on its port. */
|
|
131
|
+
static isRunning() {
|
|
132
|
+
return _global[SINGLETON_KEY]?.running ?? false;
|
|
133
|
+
}
|
|
134
|
+
/** Mark application startup as completed (switches `/startupz` to 200). */
|
|
135
|
+
static markStartupComplete() {
|
|
136
|
+
const instance = _global[SINGLETON_KEY];
|
|
137
|
+
if (!instance) return;
|
|
138
|
+
instance.startupComplete = true;
|
|
139
|
+
}
|
|
140
|
+
/** Whether application startup has completed (for `/startupz`). */
|
|
141
|
+
static isStartupComplete() {
|
|
142
|
+
return _global[SINGLETON_KEY]?.startupComplete ?? false;
|
|
131
143
|
}
|
|
132
144
|
/** Mark the service as ready / not-ready for traffic. */
|
|
133
145
|
static setReady(ready) {
|
|
@@ -288,7 +300,7 @@ var HealthzServer = class _HealthzServer {
|
|
|
288
300
|
}
|
|
289
301
|
/** `/startupz` — Startup probe */
|
|
290
302
|
handleStartup(res, isHead = false) {
|
|
291
|
-
if (this.
|
|
303
|
+
if (this.startupComplete) {
|
|
292
304
|
this.sendProbe(res, 200, "ok", [], isHead);
|
|
293
305
|
} else {
|
|
294
306
|
this.sendProbe(res, 503, "unhealthy", [
|
|
@@ -296,7 +308,7 @@ var HealthzServer = class _HealthzServer {
|
|
|
296
308
|
name: "startup",
|
|
297
309
|
ok: false,
|
|
298
310
|
durationMs: 0,
|
|
299
|
-
error: "
|
|
311
|
+
error: "Application startup not complete"
|
|
300
312
|
}
|
|
301
313
|
], isHead);
|
|
302
314
|
}
|
|
@@ -383,7 +395,8 @@ var HealthzServer = class _HealthzServer {
|
|
|
383
395
|
process.off("SIGTERM", this.onSigterm);
|
|
384
396
|
process.off("SIGINT", this.onSigint);
|
|
385
397
|
this.ready = false;
|
|
386
|
-
this.
|
|
398
|
+
this.startupComplete = false;
|
|
399
|
+
this.running = false;
|
|
387
400
|
this.shuttingDown = false;
|
|
388
401
|
}
|
|
389
402
|
};
|
|
@@ -219,7 +219,7 @@ declare function resolveConfig(userConfig?: CatbeeHealthzServerConfig): Resolved
|
|
|
219
219
|
* |-----------|-------------|------------------|-----------|
|
|
220
220
|
* | Liveness | `/healthz` | `livenessProbe` | Runs configured checks; 200 = alive, 503 = unhealthy |
|
|
221
221
|
* | Readiness | `/readyz` | `readinessProbe` | Checks readiness flag + readiness checks; 503 while not ready |
|
|
222
|
-
* | Startup | `/startupz` | `startupProbe` | 200 once
|
|
222
|
+
* | Startup | `/startupz` | `startupProbe` | 200 once application startup completes (`markStartupComplete()`); 503 while booting |
|
|
223
223
|
*
|
|
224
224
|
* ### Kubernetes Probe Best Practices:
|
|
225
225
|
* - **Liveness (`/healthz`)**: Keep these checks extremely lightweight (e.g. process is responsive,
|
|
@@ -229,7 +229,8 @@ declare function resolveConfig(userConfig?: CatbeeHealthzServerConfig): Resolved
|
|
|
229
229
|
* - **Readiness (`/readyz`)**: Place external dependency checks (DB, Redis, downstream APIs) here via
|
|
230
230
|
* `readinessChecks`. If a dependency fails, Kubernetes temporarily pulls the pod from service endpoints
|
|
231
231
|
* without restarting the container, allowing it to recover gracefully.
|
|
232
|
-
* - **Startup (`/startupz`)**: Verifies the
|
|
232
|
+
* - **Startup (`/startupz`)**: Verifies the application has completed its startup sequence
|
|
233
|
+
* (signaled via `markStartupComplete()`). Protects slow-starting applications from premature liveness kills.
|
|
233
234
|
*
|
|
234
235
|
* @example
|
|
235
236
|
* ```ts
|
|
@@ -237,11 +238,9 @@ declare function resolveConfig(userConfig?: CatbeeHealthzServerConfig): Resolved
|
|
|
237
238
|
*
|
|
238
239
|
* const addr = await HealthzServer.start({
|
|
239
240
|
* port: 8282,
|
|
240
|
-
* // Keep liveness lightweight:
|
|
241
241
|
* checks: [
|
|
242
242
|
* { name: 'process', check: () => true },
|
|
243
243
|
* ],
|
|
244
|
-
* // Place dependency checks on readiness:
|
|
245
244
|
* readinessChecks: [
|
|
246
245
|
* { name: 'database', check: (signal) => db.ping({ signal }) },
|
|
247
246
|
* { name: 'redis', check: (signal) => redis.ping({ signal }) },
|
|
@@ -249,6 +248,9 @@ declare function resolveConfig(userConfig?: CatbeeHealthzServerConfig): Resolved
|
|
|
249
248
|
* shutdownDelayMs: 10_000,
|
|
250
249
|
* });
|
|
251
250
|
*
|
|
251
|
+
* // Signal application startup complete (switches /startupz to 200):
|
|
252
|
+
* HealthzServer.markStartupComplete();
|
|
253
|
+
*
|
|
252
254
|
* // Signal readiness after all background services and migrations are ready:
|
|
253
255
|
* HealthzServer.setReady(true);
|
|
254
256
|
* ```
|
|
@@ -257,8 +259,10 @@ declare class HealthzServer {
|
|
|
257
259
|
private readonly server;
|
|
258
260
|
private readonly config;
|
|
259
261
|
private readonly startedAt;
|
|
260
|
-
/** Whether the health server
|
|
261
|
-
private
|
|
262
|
+
/** Whether the health HTTP server is currently running and listening on its port */
|
|
263
|
+
private running;
|
|
264
|
+
/** Whether application startup has completed (for `/startupz`) */
|
|
265
|
+
private startupComplete;
|
|
262
266
|
/** Whether the service is ready to receive traffic (for `/readyz`) */
|
|
263
267
|
private ready;
|
|
264
268
|
private shuttingDown;
|
|
@@ -274,8 +278,12 @@ declare class HealthzServer {
|
|
|
274
278
|
* Returns `null` if a server is already running in this process.
|
|
275
279
|
*/
|
|
276
280
|
static start(opts?: CatbeeHealthzServerConfig): Promise<HealthzAddressInfo | null>;
|
|
277
|
-
/** Whether the
|
|
278
|
-
static
|
|
281
|
+
/** Whether the Healthz HTTP probe server is currently running and listening on its port. */
|
|
282
|
+
static isRunning(): boolean;
|
|
283
|
+
/** Mark application startup as completed (switches `/startupz` to 200). */
|
|
284
|
+
static markStartupComplete(): void;
|
|
285
|
+
/** Whether application startup has completed (for `/startupz`). */
|
|
286
|
+
static isStartupComplete(): boolean;
|
|
279
287
|
/** Mark the service as ready / not-ready for traffic. */
|
|
280
288
|
static setReady(ready: boolean): void;
|
|
281
289
|
/** Whether the service is currently marked as ready. */
|
package/healthz-server/index.mjs
CHANGED
|
@@ -65,8 +65,10 @@ var HealthzServer = class _HealthzServer {
|
|
|
65
65
|
server;
|
|
66
66
|
config;
|
|
67
67
|
startedAt = Date.now();
|
|
68
|
-
/** Whether the health server
|
|
69
|
-
|
|
68
|
+
/** Whether the health HTTP server is currently running and listening on its port */
|
|
69
|
+
running = false;
|
|
70
|
+
/** Whether application startup has completed (for `/startupz`) */
|
|
71
|
+
startupComplete = false;
|
|
70
72
|
/** Whether the service is ready to receive traffic (for `/readyz`) */
|
|
71
73
|
ready = false;
|
|
72
74
|
shuttingDown = false;
|
|
@@ -107,7 +109,7 @@ var HealthzServer = class _HealthzServer {
|
|
|
107
109
|
port: config.port
|
|
108
110
|
}, () => {
|
|
109
111
|
instance.server.off("error", onError);
|
|
110
|
-
instance.
|
|
112
|
+
instance.running = true;
|
|
111
113
|
const addr = instance.server.address();
|
|
112
114
|
if (!addr || typeof addr === "string") {
|
|
113
115
|
instance.cleanup();
|
|
@@ -123,9 +125,19 @@ var HealthzServer = class _HealthzServer {
|
|
|
123
125
|
});
|
|
124
126
|
});
|
|
125
127
|
}
|
|
126
|
-
/** Whether the
|
|
127
|
-
static
|
|
128
|
-
return _global[SINGLETON_KEY]?.
|
|
128
|
+
/** Whether the Healthz HTTP probe server is currently running and listening on its port. */
|
|
129
|
+
static isRunning() {
|
|
130
|
+
return _global[SINGLETON_KEY]?.running ?? false;
|
|
131
|
+
}
|
|
132
|
+
/** Mark application startup as completed (switches `/startupz` to 200). */
|
|
133
|
+
static markStartupComplete() {
|
|
134
|
+
const instance = _global[SINGLETON_KEY];
|
|
135
|
+
if (!instance) return;
|
|
136
|
+
instance.startupComplete = true;
|
|
137
|
+
}
|
|
138
|
+
/** Whether application startup has completed (for `/startupz`). */
|
|
139
|
+
static isStartupComplete() {
|
|
140
|
+
return _global[SINGLETON_KEY]?.startupComplete ?? false;
|
|
129
141
|
}
|
|
130
142
|
/** Mark the service as ready / not-ready for traffic. */
|
|
131
143
|
static setReady(ready) {
|
|
@@ -286,7 +298,7 @@ var HealthzServer = class _HealthzServer {
|
|
|
286
298
|
}
|
|
287
299
|
/** `/startupz` — Startup probe */
|
|
288
300
|
handleStartup(res, isHead = false) {
|
|
289
|
-
if (this.
|
|
301
|
+
if (this.startupComplete) {
|
|
290
302
|
this.sendProbe(res, 200, "ok", [], isHead);
|
|
291
303
|
} else {
|
|
292
304
|
this.sendProbe(res, 503, "unhealthy", [
|
|
@@ -294,7 +306,7 @@ var HealthzServer = class _HealthzServer {
|
|
|
294
306
|
name: "startup",
|
|
295
307
|
ok: false,
|
|
296
308
|
durationMs: 0,
|
|
297
|
-
error: "
|
|
309
|
+
error: "Application startup not complete"
|
|
298
310
|
}
|
|
299
311
|
], isHead);
|
|
300
312
|
}
|
|
@@ -381,7 +393,8 @@ var HealthzServer = class _HealthzServer {
|
|
|
381
393
|
process.off("SIGTERM", this.onSigterm);
|
|
382
394
|
process.off("SIGINT", this.onSigint);
|
|
383
395
|
this.ready = false;
|
|
384
|
-
this.
|
|
396
|
+
this.startupComplete = false;
|
|
397
|
+
this.running = false;
|
|
385
398
|
this.shuttingDown = false;
|
|
386
399
|
}
|
|
387
400
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catbee/utils",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
package/server/index.cjs
CHANGED
|
@@ -1230,6 +1230,21 @@ var ExpressServer = class {
|
|
|
1230
1230
|
return healthzServer.HealthzServer.isReady();
|
|
1231
1231
|
}
|
|
1232
1232
|
/**
|
|
1233
|
+
* Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
|
|
1234
|
+
*
|
|
1235
|
+
* @returns This instance for method chaining
|
|
1236
|
+
*/
|
|
1237
|
+
markStartupComplete() {
|
|
1238
|
+
healthzServer.HealthzServer.markStartupComplete();
|
|
1239
|
+
return this;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Whether application startup has completed on the Healthz probe server.
|
|
1243
|
+
*/
|
|
1244
|
+
isStartupComplete() {
|
|
1245
|
+
return healthzServer.HealthzServer.isStartupComplete();
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1233
1248
|
* Get the running HealthzServer instance (if started).
|
|
1234
1249
|
*/
|
|
1235
1250
|
getHealthzServer() {
|
|
@@ -1305,92 +1320,105 @@ var ExpressServer = class {
|
|
|
1305
1320
|
*/
|
|
1306
1321
|
async doStart() {
|
|
1307
1322
|
await this.initPromise;
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}, "Server runtime error");
|
|
1334
|
-
}
|
|
1335
|
-
});
|
|
1336
|
-
this.setupConnectionTracking();
|
|
1337
|
-
Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
|
|
1338
|
-
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1339
|
-
try {
|
|
1340
|
-
if (this.isHealthzServerEnabled()) {
|
|
1341
|
-
const healthzConfig = {
|
|
1342
|
-
...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
|
|
1343
|
-
handleSignals: false,
|
|
1344
|
-
checks: [
|
|
1345
|
-
...this.healthzChecks
|
|
1346
|
-
],
|
|
1347
|
-
readinessChecks: [
|
|
1348
|
-
...this.healthzReadinessChecks
|
|
1349
|
-
]
|
|
1350
|
-
};
|
|
1351
|
-
const addr = await healthzServer.HealthzServer.start(healthzConfig);
|
|
1352
|
-
if (!addr) {
|
|
1353
|
-
throw new Error("Healthz probe server failed to start (already running in this process)");
|
|
1354
|
-
}
|
|
1355
|
-
this.healthzAddress = addr;
|
|
1356
|
-
}
|
|
1357
|
-
this.logServerStartInfo();
|
|
1358
|
-
await this.runHook("afterStart", server);
|
|
1359
|
-
if (this.isHealthzServerEnabled()) {
|
|
1360
|
-
healthzServer.HealthzServer.setReady(true);
|
|
1361
|
-
}
|
|
1362
|
-
isListening = true;
|
|
1363
|
-
resolve(server);
|
|
1364
|
-
} catch (err) {
|
|
1365
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1323
|
+
if (this.isHealthzServerEnabled()) {
|
|
1324
|
+
const healthzConfig = {
|
|
1325
|
+
...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
|
|
1326
|
+
handleSignals: false,
|
|
1327
|
+
checks: [
|
|
1328
|
+
...this.healthzChecks
|
|
1329
|
+
],
|
|
1330
|
+
readinessChecks: [
|
|
1331
|
+
...this.healthzReadinessChecks
|
|
1332
|
+
]
|
|
1333
|
+
};
|
|
1334
|
+
const addr = await healthzServer.HealthzServer.start(healthzConfig);
|
|
1335
|
+
if (!addr) {
|
|
1336
|
+
throw new Error("Healthz probe server failed to start (already running in this process)");
|
|
1337
|
+
}
|
|
1338
|
+
this.healthzAddress = addr;
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
await this.runHook("beforeStart", this.app);
|
|
1342
|
+
const server = this.createServerInstance();
|
|
1343
|
+
this.server = server;
|
|
1344
|
+
return await new Promise((resolve, reject) => {
|
|
1345
|
+
let isListening = false;
|
|
1346
|
+
server.on("error", async (err) => {
|
|
1347
|
+
if (!isListening) {
|
|
1366
1348
|
logger.getLogger().error({
|
|
1367
|
-
err
|
|
1368
|
-
}, "Server
|
|
1369
|
-
if (healthzServer.HealthzServer.isStarted()) {
|
|
1370
|
-
await healthzServer.HealthzServer.stop().catch(() => {
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1349
|
+
err
|
|
1350
|
+
}, "Server failed to start");
|
|
1373
1351
|
try {
|
|
1374
1352
|
server.removeAllListeners();
|
|
1375
1353
|
server.close();
|
|
1354
|
+
if (healthzServer.HealthzServer.isRunning()) {
|
|
1355
|
+
await healthzServer.HealthzServer.stop().catch(() => {
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1376
1358
|
} catch {
|
|
1377
1359
|
}
|
|
1378
1360
|
this.server = null;
|
|
1379
1361
|
this.healthzAddress = null;
|
|
1380
1362
|
this.connections.clear();
|
|
1381
|
-
reject(
|
|
1363
|
+
reject(err);
|
|
1364
|
+
} else {
|
|
1365
|
+
logger.getLogger().error({
|
|
1366
|
+
err
|
|
1367
|
+
}, "Server runtime error");
|
|
1382
1368
|
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1369
|
+
});
|
|
1370
|
+
this.setupConnectionTracking();
|
|
1371
|
+
Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
|
|
1372
|
+
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1373
|
+
try {
|
|
1374
|
+
this.logServerStartInfo();
|
|
1375
|
+
await this.runHook("afterStart", server);
|
|
1376
|
+
if (this.isHealthzServerEnabled()) {
|
|
1377
|
+
healthzServer.HealthzServer.markStartupComplete();
|
|
1378
|
+
healthzServer.HealthzServer.setReady(true);
|
|
1379
|
+
}
|
|
1380
|
+
isListening = true;
|
|
1381
|
+
resolve(server);
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1384
|
+
logger.getLogger().error({
|
|
1385
|
+
err: error
|
|
1386
|
+
}, "Server startup failed");
|
|
1387
|
+
if (healthzServer.HealthzServer.isRunning()) {
|
|
1388
|
+
await healthzServer.HealthzServer.stop().catch(() => {
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
server.removeAllListeners();
|
|
1393
|
+
server.close();
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
this.server = null;
|
|
1397
|
+
this.healthzAddress = null;
|
|
1398
|
+
this.connections.clear();
|
|
1399
|
+
reject(error);
|
|
1400
|
+
}
|
|
1401
|
+
}, "onListening");
|
|
1402
|
+
const listenArgs = [
|
|
1403
|
+
this.config.port,
|
|
1404
|
+
this.config.host,
|
|
1405
|
+
onListening
|
|
1406
|
+
];
|
|
1407
|
+
server.listen(...listenArgs);
|
|
1408
|
+
}).catch((err) => {
|
|
1409
|
+
server.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
1410
|
+
});
|
|
1392
1411
|
});
|
|
1393
|
-
})
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
if (healthzServer.HealthzServer.isRunning()) {
|
|
1414
|
+
await healthzServer.HealthzServer.stop().catch(() => {
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
this.healthzAddress = null;
|
|
1418
|
+
this.server = null;
|
|
1419
|
+
this.connections.clear();
|
|
1420
|
+
throw err;
|
|
1421
|
+
}
|
|
1394
1422
|
}
|
|
1395
1423
|
/**
|
|
1396
1424
|
* Create HTTP or HTTPS server instance (without listening).
|
|
@@ -1454,7 +1482,7 @@ var ExpressServer = class {
|
|
|
1454
1482
|
* - Monitoring systems are notified
|
|
1455
1483
|
*/
|
|
1456
1484
|
async stop(force = false) {
|
|
1457
|
-
if (!this.server && !healthzServer.HealthzServer.
|
|
1485
|
+
if (!this.server && !healthzServer.HealthzServer.isRunning()) {
|
|
1458
1486
|
logger.getLogger().warn("Stop called but server is not running");
|
|
1459
1487
|
return;
|
|
1460
1488
|
}
|
|
@@ -1463,7 +1491,7 @@ var ExpressServer = class {
|
|
|
1463
1491
|
return;
|
|
1464
1492
|
}
|
|
1465
1493
|
this.isShuttingDown = true;
|
|
1466
|
-
if (healthzServer.HealthzServer.
|
|
1494
|
+
if (healthzServer.HealthzServer.isRunning()) {
|
|
1467
1495
|
healthzServer.HealthzServer.setReady(false);
|
|
1468
1496
|
}
|
|
1469
1497
|
if (this.server) {
|
|
@@ -1471,7 +1499,7 @@ var ExpressServer = class {
|
|
|
1471
1499
|
}
|
|
1472
1500
|
try {
|
|
1473
1501
|
const shutdownDelay = this.getHealthzShutdownDelay();
|
|
1474
|
-
if (shutdownDelay > 0 && !force && healthzServer.HealthzServer.
|
|
1502
|
+
if (shutdownDelay > 0 && !force && healthzServer.HealthzServer.isRunning()) {
|
|
1475
1503
|
logger.getLogger().info(`Waiting ${shutdownDelay}ms for load balancer to drain traffic...`);
|
|
1476
1504
|
await new Promise((resolve) => setTimeout(resolve, shutdownDelay));
|
|
1477
1505
|
}
|
|
@@ -1479,7 +1507,7 @@ var ExpressServer = class {
|
|
|
1479
1507
|
await this.gracefulShutdown(force);
|
|
1480
1508
|
}
|
|
1481
1509
|
} finally {
|
|
1482
|
-
if (healthzServer.HealthzServer.
|
|
1510
|
+
if (healthzServer.HealthzServer.isRunning()) {
|
|
1483
1511
|
await healthzServer.HealthzServer.stop();
|
|
1484
1512
|
this.healthzAddress = null;
|
|
1485
1513
|
}
|
package/server/index.d.ts
CHANGED
|
@@ -237,6 +237,16 @@ declare class ExpressServer {
|
|
|
237
237
|
* Whether the service is currently marked as ready for traffic on the Healthz probe server.
|
|
238
238
|
*/
|
|
239
239
|
isReady(): boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
|
|
242
|
+
*
|
|
243
|
+
* @returns This instance for method chaining
|
|
244
|
+
*/
|
|
245
|
+
markStartupComplete(): this;
|
|
246
|
+
/**
|
|
247
|
+
* Whether application startup has completed on the Healthz probe server.
|
|
248
|
+
*/
|
|
249
|
+
isStartupComplete(): boolean;
|
|
240
250
|
/**
|
|
241
251
|
* Get the running HealthzServer instance (if started).
|
|
242
252
|
*/
|
package/server/index.mjs
CHANGED
|
@@ -1222,6 +1222,21 @@ var ExpressServer = class {
|
|
|
1222
1222
|
return HealthzServer.isReady();
|
|
1223
1223
|
}
|
|
1224
1224
|
/**
|
|
1225
|
+
* Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
|
|
1226
|
+
*
|
|
1227
|
+
* @returns This instance for method chaining
|
|
1228
|
+
*/
|
|
1229
|
+
markStartupComplete() {
|
|
1230
|
+
HealthzServer.markStartupComplete();
|
|
1231
|
+
return this;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Whether application startup has completed on the Healthz probe server.
|
|
1235
|
+
*/
|
|
1236
|
+
isStartupComplete() {
|
|
1237
|
+
return HealthzServer.isStartupComplete();
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1225
1240
|
* Get the running HealthzServer instance (if started).
|
|
1226
1241
|
*/
|
|
1227
1242
|
getHealthzServer() {
|
|
@@ -1297,92 +1312,105 @@ var ExpressServer = class {
|
|
|
1297
1312
|
*/
|
|
1298
1313
|
async doStart() {
|
|
1299
1314
|
await this.initPromise;
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
}, "Server runtime error");
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
this.setupConnectionTracking();
|
|
1329
|
-
Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
|
|
1330
|
-
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1331
|
-
try {
|
|
1332
|
-
if (this.isHealthzServerEnabled()) {
|
|
1333
|
-
const healthzConfig = {
|
|
1334
|
-
...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
|
|
1335
|
-
handleSignals: false,
|
|
1336
|
-
checks: [
|
|
1337
|
-
...this.healthzChecks
|
|
1338
|
-
],
|
|
1339
|
-
readinessChecks: [
|
|
1340
|
-
...this.healthzReadinessChecks
|
|
1341
|
-
]
|
|
1342
|
-
};
|
|
1343
|
-
const addr = await HealthzServer.start(healthzConfig);
|
|
1344
|
-
if (!addr) {
|
|
1345
|
-
throw new Error("Healthz probe server failed to start (already running in this process)");
|
|
1346
|
-
}
|
|
1347
|
-
this.healthzAddress = addr;
|
|
1348
|
-
}
|
|
1349
|
-
this.logServerStartInfo();
|
|
1350
|
-
await this.runHook("afterStart", server);
|
|
1351
|
-
if (this.isHealthzServerEnabled()) {
|
|
1352
|
-
HealthzServer.setReady(true);
|
|
1353
|
-
}
|
|
1354
|
-
isListening = true;
|
|
1355
|
-
resolve(server);
|
|
1356
|
-
} catch (err) {
|
|
1357
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1315
|
+
if (this.isHealthzServerEnabled()) {
|
|
1316
|
+
const healthzConfig = {
|
|
1317
|
+
...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
|
|
1318
|
+
handleSignals: false,
|
|
1319
|
+
checks: [
|
|
1320
|
+
...this.healthzChecks
|
|
1321
|
+
],
|
|
1322
|
+
readinessChecks: [
|
|
1323
|
+
...this.healthzReadinessChecks
|
|
1324
|
+
]
|
|
1325
|
+
};
|
|
1326
|
+
const addr = await HealthzServer.start(healthzConfig);
|
|
1327
|
+
if (!addr) {
|
|
1328
|
+
throw new Error("Healthz probe server failed to start (already running in this process)");
|
|
1329
|
+
}
|
|
1330
|
+
this.healthzAddress = addr;
|
|
1331
|
+
}
|
|
1332
|
+
try {
|
|
1333
|
+
await this.runHook("beforeStart", this.app);
|
|
1334
|
+
const server = this.createServerInstance();
|
|
1335
|
+
this.server = server;
|
|
1336
|
+
return await new Promise((resolve, reject) => {
|
|
1337
|
+
let isListening = false;
|
|
1338
|
+
server.on("error", async (err) => {
|
|
1339
|
+
if (!isListening) {
|
|
1358
1340
|
getLogger().error({
|
|
1359
|
-
err
|
|
1360
|
-
}, "Server
|
|
1361
|
-
if (HealthzServer.isStarted()) {
|
|
1362
|
-
await HealthzServer.stop().catch(() => {
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1341
|
+
err
|
|
1342
|
+
}, "Server failed to start");
|
|
1365
1343
|
try {
|
|
1366
1344
|
server.removeAllListeners();
|
|
1367
1345
|
server.close();
|
|
1346
|
+
if (HealthzServer.isRunning()) {
|
|
1347
|
+
await HealthzServer.stop().catch(() => {
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1368
1350
|
} catch {
|
|
1369
1351
|
}
|
|
1370
1352
|
this.server = null;
|
|
1371
1353
|
this.healthzAddress = null;
|
|
1372
1354
|
this.connections.clear();
|
|
1373
|
-
reject(
|
|
1355
|
+
reject(err);
|
|
1356
|
+
} else {
|
|
1357
|
+
getLogger().error({
|
|
1358
|
+
err
|
|
1359
|
+
}, "Server runtime error");
|
|
1374
1360
|
}
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1361
|
+
});
|
|
1362
|
+
this.setupConnectionTracking();
|
|
1363
|
+
Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
|
|
1364
|
+
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1365
|
+
try {
|
|
1366
|
+
this.logServerStartInfo();
|
|
1367
|
+
await this.runHook("afterStart", server);
|
|
1368
|
+
if (this.isHealthzServerEnabled()) {
|
|
1369
|
+
HealthzServer.markStartupComplete();
|
|
1370
|
+
HealthzServer.setReady(true);
|
|
1371
|
+
}
|
|
1372
|
+
isListening = true;
|
|
1373
|
+
resolve(server);
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1376
|
+
getLogger().error({
|
|
1377
|
+
err: error
|
|
1378
|
+
}, "Server startup failed");
|
|
1379
|
+
if (HealthzServer.isRunning()) {
|
|
1380
|
+
await HealthzServer.stop().catch(() => {
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
try {
|
|
1384
|
+
server.removeAllListeners();
|
|
1385
|
+
server.close();
|
|
1386
|
+
} catch {
|
|
1387
|
+
}
|
|
1388
|
+
this.server = null;
|
|
1389
|
+
this.healthzAddress = null;
|
|
1390
|
+
this.connections.clear();
|
|
1391
|
+
reject(error);
|
|
1392
|
+
}
|
|
1393
|
+
}, "onListening");
|
|
1394
|
+
const listenArgs = [
|
|
1395
|
+
this.config.port,
|
|
1396
|
+
this.config.host,
|
|
1397
|
+
onListening
|
|
1398
|
+
];
|
|
1399
|
+
server.listen(...listenArgs);
|
|
1400
|
+
}).catch((err) => {
|
|
1401
|
+
server.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
1402
|
+
});
|
|
1384
1403
|
});
|
|
1385
|
-
})
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
if (HealthzServer.isRunning()) {
|
|
1406
|
+
await HealthzServer.stop().catch(() => {
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
this.healthzAddress = null;
|
|
1410
|
+
this.server = null;
|
|
1411
|
+
this.connections.clear();
|
|
1412
|
+
throw err;
|
|
1413
|
+
}
|
|
1386
1414
|
}
|
|
1387
1415
|
/**
|
|
1388
1416
|
* Create HTTP or HTTPS server instance (without listening).
|
|
@@ -1446,7 +1474,7 @@ var ExpressServer = class {
|
|
|
1446
1474
|
* - Monitoring systems are notified
|
|
1447
1475
|
*/
|
|
1448
1476
|
async stop(force = false) {
|
|
1449
|
-
if (!this.server && !HealthzServer.
|
|
1477
|
+
if (!this.server && !HealthzServer.isRunning()) {
|
|
1450
1478
|
getLogger().warn("Stop called but server is not running");
|
|
1451
1479
|
return;
|
|
1452
1480
|
}
|
|
@@ -1455,7 +1483,7 @@ var ExpressServer = class {
|
|
|
1455
1483
|
return;
|
|
1456
1484
|
}
|
|
1457
1485
|
this.isShuttingDown = true;
|
|
1458
|
-
if (HealthzServer.
|
|
1486
|
+
if (HealthzServer.isRunning()) {
|
|
1459
1487
|
HealthzServer.setReady(false);
|
|
1460
1488
|
}
|
|
1461
1489
|
if (this.server) {
|
|
@@ -1463,7 +1491,7 @@ var ExpressServer = class {
|
|
|
1463
1491
|
}
|
|
1464
1492
|
try {
|
|
1465
1493
|
const shutdownDelay = this.getHealthzShutdownDelay();
|
|
1466
|
-
if (shutdownDelay > 0 && !force && HealthzServer.
|
|
1494
|
+
if (shutdownDelay > 0 && !force && HealthzServer.isRunning()) {
|
|
1467
1495
|
getLogger().info(`Waiting ${shutdownDelay}ms for load balancer to drain traffic...`);
|
|
1468
1496
|
await new Promise((resolve) => setTimeout(resolve, shutdownDelay));
|
|
1469
1497
|
}
|
|
@@ -1471,7 +1499,7 @@ var ExpressServer = class {
|
|
|
1471
1499
|
await this.gracefulShutdown(force);
|
|
1472
1500
|
}
|
|
1473
1501
|
} finally {
|
|
1474
|
-
if (HealthzServer.
|
|
1502
|
+
if (HealthzServer.isRunning()) {
|
|
1475
1503
|
await HealthzServer.stop();
|
|
1476
1504
|
this.healthzAddress = null;
|
|
1477
1505
|
}
|