@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.
@@ -67,8 +67,10 @@ var HealthzServer = class _HealthzServer {
67
67
  server;
68
68
  config;
69
69
  startedAt = Date.now();
70
- /** Whether the health server has successfully started listening (for `/startupz`) */
71
- started = false;
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.started = true;
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 health-check server is currently running and started. */
129
- static isStarted() {
130
- return _global[SINGLETON_KEY]?.started ?? false;
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.started) {
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: "Service has not started yet"
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.started = false;
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 the server has successfully started listening; 503 before that |
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 health server process has started listening.
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 has successfully started listening (for `/startupz`) */
261
- private started;
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 health-check server is currently running and started. */
278
- static isStarted(): boolean;
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. */
@@ -65,8 +65,10 @@ var HealthzServer = class _HealthzServer {
65
65
  server;
66
66
  config;
67
67
  startedAt = Date.now();
68
- /** Whether the health server has successfully started listening (for `/startupz`) */
69
- started = false;
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.started = true;
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 health-check server is currently running and started. */
127
- static isStarted() {
128
- return _global[SINGLETON_KEY]?.started ?? false;
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.started) {
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: "Service has not started yet"
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.started = false;
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.0",
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
- await this.runHook("beforeStart", this.app);
1309
- const server = this.createServerInstance();
1310
- this.server = server;
1311
- return new Promise((resolve, reject) => {
1312
- let isListening = false;
1313
- server.on("error", (err) => {
1314
- if (!isListening) {
1315
- logger.getLogger().error({
1316
- err
1317
- }, "Server failed to start");
1318
- try {
1319
- server.removeAllListeners();
1320
- server.close();
1321
- if (healthzServer.HealthzServer.isStarted()) {
1322
- healthzServer.HealthzServer.stop().catch(() => {
1323
- });
1324
- }
1325
- } catch {
1326
- }
1327
- this.server = null;
1328
- this.connections.clear();
1329
- reject(err);
1330
- } else {
1331
- logger.getLogger().error({
1332
- err
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: error
1368
- }, "Server startup failed");
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(error);
1363
+ reject(err);
1364
+ } else {
1365
+ logger.getLogger().error({
1366
+ err
1367
+ }, "Server runtime error");
1382
1368
  }
1383
- }, "onListening");
1384
- const listenArgs = [
1385
- this.config.port,
1386
- this.config.host,
1387
- onListening
1388
- ];
1389
- server.listen(...listenArgs);
1390
- }).catch((err) => {
1391
- server.emit("error", err instanceof Error ? err : new Error(String(err)));
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.isStarted()) {
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.isStarted()) {
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.isStarted()) {
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.isStarted()) {
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
- await this.runHook("beforeStart", this.app);
1301
- const server = this.createServerInstance();
1302
- this.server = server;
1303
- return new Promise((resolve, reject) => {
1304
- let isListening = false;
1305
- server.on("error", (err) => {
1306
- if (!isListening) {
1307
- getLogger().error({
1308
- err
1309
- }, "Server failed to start");
1310
- try {
1311
- server.removeAllListeners();
1312
- server.close();
1313
- if (HealthzServer.isStarted()) {
1314
- HealthzServer.stop().catch(() => {
1315
- });
1316
- }
1317
- } catch {
1318
- }
1319
- this.server = null;
1320
- this.connections.clear();
1321
- reject(err);
1322
- } else {
1323
- getLogger().error({
1324
- err
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: error
1360
- }, "Server startup failed");
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(error);
1355
+ reject(err);
1356
+ } else {
1357
+ getLogger().error({
1358
+ err
1359
+ }, "Server runtime error");
1374
1360
  }
1375
- }, "onListening");
1376
- const listenArgs = [
1377
- this.config.port,
1378
- this.config.host,
1379
- onListening
1380
- ];
1381
- server.listen(...listenArgs);
1382
- }).catch((err) => {
1383
- server.emit("error", err instanceof Error ? err : new Error(String(err)));
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.isStarted()) {
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.isStarted()) {
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.isStarted()) {
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.isStarted()) {
1502
+ if (HealthzServer.isRunning()) {
1475
1503
  await HealthzServer.stop();
1476
1504
  this.healthzAddress = null;
1477
1505
  }