@catbee/utils 2.0.1 → 2.0.3

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/server/index.cjs CHANGED
@@ -57,17 +57,29 @@ var ServerConfigBuilder = class {
57
57
  *
58
58
  * @private
59
59
  * @param port - The port number to validate
60
- * @throws {Error} If port is not an integer or is outside the valid range (1-65535)
60
+ * @throws {Error} If port is not an integer or is outside the valid range (0-65535)
61
61
  */
62
62
  validatePort(port) {
63
- if (!validation.isPort(port)) {
64
- throw new Error(`Port must be a valid number between 1 and 65535, got: ${port}`);
63
+ if (!validation.isPort(port, true)) {
64
+ throw new Error(`Port must be a valid number between 0 and 65535, got: ${port}`);
65
+ }
66
+ }
67
+ /**
68
+ * Validates that a hostname is valid.
69
+ *
70
+ * @private
71
+ * @param host - The hostname to validate
72
+ * @throws {Error} If hostname is invalid
73
+ */
74
+ validateHost(host) {
75
+ if (!validation.isHostname(host)) {
76
+ throw new Error(`Host must be a valid hostname or IP address, got: ${host}`);
65
77
  }
66
78
  }
67
79
  /**
68
80
  * Sets the port the server will listen on.
69
81
  *
70
- * @param port - The port number (1-65535)
82
+ * @param port - The port number (0-65535). Use 0 for dynamic port assignment.
71
83
  * @returns The builder instance for chaining
72
84
  * @throws {Error} If port is invalid
73
85
  * @default 3000 (can be overridden via PORT env variable)
@@ -87,14 +99,17 @@ var ServerConfigBuilder = class {
87
99
  *
88
100
  * @param host - The hostname (e.g., 'localhost', '0.0.0.0', '127.0.0.1')
89
101
  * @returns The builder instance for chaining
102
+ * @throws {Error} If hostname is invalid
90
103
  * @default '0.0.0.0' (can be overridden via HOST env variable)
91
104
  *
92
105
  * @example
93
106
  * ```typescript
94
107
  * builder.withHost('0.0.0.0') // Listen on all interfaces
108
+ * builder.withHost('localhost') // Listen on localhost only
95
109
  * ```
96
110
  */
97
111
  withHost(host) {
112
+ this.validateHost(host);
98
113
  this.config.host = host;
99
114
  return this;
100
115
  }
@@ -505,12 +520,23 @@ var ServerConfigBuilder = class {
505
520
  * ```
506
521
  */
507
522
  withBodyParser(opts) {
523
+ if (opts === true) {
524
+ this.config.bodyParser = config.getCatbeeServerGlobalConfig().bodyParser;
525
+ return this;
526
+ } else if (opts === false) {
527
+ this.config.bodyParser = false;
528
+ return this;
529
+ }
508
530
  this.config.bodyParser = {
509
531
  ...this.config.bodyParser,
510
532
  ...opts
511
533
  };
512
534
  return this;
513
535
  }
536
+ disableBodyParser() {
537
+ this.config.bodyParser = false;
538
+ return this;
539
+ }
514
540
  /**
515
541
  * Configures cookie parsing middleware.
516
542
  *
@@ -661,7 +687,7 @@ var ServerConfigBuilder = class {
661
687
  });
662
688
  }
663
689
  mergeConfig(key, value) {
664
- const current = this.config[key] && typeof this.config[key] === "object" ? object.deepClone(this.config[key]) : {};
690
+ const current = object.isPlainObject(this.config[key]) ? object.deepClone(this.config[key]) : {};
665
691
  this.config[key] = object.deepObjMerge({}, current, value);
666
692
  }
667
693
  setEnabled(key, enable, overrides = {}) {
@@ -742,12 +768,15 @@ var ExpressServer = class {
742
768
  */
743
769
  constructor(config$1, hooks = {}) {
744
770
  if (this.hasBuildMarker(config$1)) {
745
- this.config = config$1;
771
+ this.config = object.deepObjMerge({}, config$1);
746
772
  } else {
747
773
  this.config = object.deepObjMerge({}, config.getCatbeeServerGlobalConfig(), config$1);
748
774
  }
749
- if (!validation.isPort(this.config.port)) {
750
- const msg = `Port must be a valid number between 1 and 65535, got: ${this.config.port}`;
775
+ if (this.config.host) {
776
+ this.config.host = this.normalizeHost(this.config.host);
777
+ }
778
+ if (!validation.isPort(this.config.port, true)) {
779
+ const msg = `Port must be a valid number between 0 and 65535, got: ${this.config.port}`;
751
780
  logger.getLogger().error(msg);
752
781
  throw new Error(msg);
753
782
  }
@@ -1026,7 +1055,6 @@ var ExpressServer = class {
1026
1055
  }
1027
1056
  const logger$1 = logger.getLogger();
1028
1057
  const incomingRequestMetaData = {
1029
- requestId: req.id,
1030
1058
  method: req.method,
1031
1059
  url: req.originalUrl || req.url,
1032
1060
  ip: req.ip
@@ -1076,13 +1104,23 @@ var ExpressServer = class {
1076
1104
  * Set up body parsing middleware.
1077
1105
  */
1078
1106
  setupBodyParsingMiddleware() {
1079
- if (this.config.bodyParser) {
1107
+ if (object.isPlainObject(this.config.bodyParser)) {
1080
1108
  if (this.config.bodyParser.json) {
1081
1109
  this.app.use(express__default.default.json(this.config.bodyParser.json));
1082
1110
  }
1083
1111
  if (this.config.bodyParser.urlencoded) {
1084
1112
  this.app.use(express__default.default.urlencoded(this.config.bodyParser.urlencoded));
1085
1113
  }
1114
+ } else if (this.config.bodyParser === true) {
1115
+ const globalBodyParserConfig = config.getCatbeeServerGlobalConfig().bodyParser;
1116
+ if (object.isPlainObject(globalBodyParserConfig)) {
1117
+ if (globalBodyParserConfig.json) {
1118
+ this.app.use(express__default.default.json(globalBodyParserConfig.json));
1119
+ }
1120
+ if (globalBodyParserConfig.urlencoded) {
1121
+ this.app.use(express__default.default.urlencoded(globalBodyParserConfig.urlencoded));
1122
+ }
1123
+ }
1086
1124
  }
1087
1125
  }
1088
1126
  /**
@@ -1421,7 +1459,9 @@ var ExpressServer = class {
1421
1459
  */
1422
1460
  logServerStartInfo() {
1423
1461
  const protocol = this.config.https ? "https" : "http";
1424
- const url = `${protocol}://${this.config.host}:${this.config.port}`;
1462
+ const port = this.getPort();
1463
+ const host = this.formatHostForUrl(this.config.host || "localhost");
1464
+ const url = `${protocol}://${host}:${port}`;
1425
1465
  logger.getLogger().info(`Server running on ${url}`);
1426
1466
  if (this.config.healthCheck?.path) {
1427
1467
  logger.getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
@@ -1646,12 +1686,136 @@ var ExpressServer = class {
1646
1686
  return this.config;
1647
1687
  }
1648
1688
  /**
1689
+ * Get the port the server is listening on.
1690
+ * Returns the actual port if server is running (useful when config.port was 0),
1691
+ * otherwise returns the configured port.
1692
+ *
1693
+ * @returns The port number
1694
+ */
1695
+ getPort() {
1696
+ const address = this.server?.address();
1697
+ if (address && typeof address === "object" && "port" in address) {
1698
+ return address.port;
1699
+ }
1700
+ return this.config.port;
1701
+ }
1702
+ /**
1703
+ * Get the full URL the server is running on.
1704
+ * Returns the actual URL if server is running (useful when config.port was 0),
1705
+ * otherwise returns the configured URL.
1706
+ *
1707
+ * @returns The full server URL (e.g., "http://localhost:3000")
1708
+ */
1709
+ getUrl() {
1710
+ const protocol = this.config.https ? "https" : "http";
1711
+ const port = this.getPort();
1712
+ const host = this.formatHostForUrl(this.config.host || "localhost");
1713
+ return `${protocol}://${host}:${port}`;
1714
+ }
1715
+ /**
1716
+ * Check if the server is configured for dynamic port assignment.
1717
+ * Returns true if the original port configuration was 0.
1718
+ *
1719
+ * @returns True if using dynamic port assignment, false otherwise
1720
+ */
1721
+ isPortDynamic() {
1722
+ return this.config.port === 0;
1723
+ }
1724
+ /**
1725
+ * Check if the server is currently running and listening for requests.
1726
+ *
1727
+ * @returns True if server is running, false otherwise
1728
+ */
1729
+ isRunning() {
1730
+ return this.server !== null && this.server.listening;
1731
+ }
1732
+ /**
1733
+ * Get the host address the server is bound to.
1734
+ *
1735
+ * @returns The host address
1736
+ */
1737
+ getHost() {
1738
+ return this.config.host || "localhost";
1739
+ }
1740
+ /**
1741
+ * Get the protocol the server is using ('http' or 'https').
1742
+ *
1743
+ * @returns The protocol string
1744
+ */
1745
+ getProtocol() {
1746
+ return this.config.https ? "https" : "http";
1747
+ }
1748
+ /**
1749
+ * Check if the server is configured to use HTTPS.
1750
+ *
1751
+ * @returns True if using HTTPS, false otherwise
1752
+ */
1753
+ isHttps() {
1754
+ return this.config.https !== void 0;
1755
+ }
1756
+ /**
1757
+ * Set a new port for the server.
1758
+ * Can only be called before the server starts listening.
1759
+ * Useful for testing scenarios where you need to change the port dynamically.
1760
+ *
1761
+ * @param port - The new port number (0-65535)
1762
+ * @throws Error if server is already running or port is invalid
1763
+ */
1764
+ setPort(port) {
1765
+ if (this.server) {
1766
+ throw new Error("Cannot change port after server has started");
1767
+ }
1768
+ if (!validation.isPort(port, true)) {
1769
+ throw new Error(`Port must be a valid number between 0 and 65535, got: ${port}`);
1770
+ }
1771
+ this.config.port = port;
1772
+ }
1773
+ /**
1774
+ * Set a new host for the server.
1775
+ * Can only be called before the server starts listening.
1776
+ * Useful for testing scenarios where you need to change the host dynamically.
1777
+ *
1778
+ * @param host - The new host (e.g., "localhost", "0.0.0.0", "127.0.0.1", "::1", or "[::1]")
1779
+ * @throws {Error} If server is already running or host is invalid
1780
+ */
1781
+ setHost(host) {
1782
+ if (this.server) {
1783
+ throw new Error("Cannot change host after server has started");
1784
+ }
1785
+ const normalizedHost = this.normalizeHost(host);
1786
+ if (!validation.isHostname(normalizedHost)) {
1787
+ throw new Error(`Host must be a valid hostname or IP address, got: ${host}`);
1788
+ }
1789
+ this.config.host = normalizedHost;
1790
+ }
1791
+ /**
1649
1792
  * Wait until server initialization (middleware + routes) has completed.
1650
1793
  * Useful for integration tests that inspect app before starting.
1651
1794
  */
1652
1795
  async waitUntilReady() {
1653
1796
  await this.initPromise;
1654
1797
  }
1798
+ /**
1799
+ * Normalize host by stripping surrounding brackets from IPv6 addresses.
1800
+ * This ensures the host value is compatible with server.listen().
1801
+ * Brackets are URL syntax only and must be removed for Node.js binding.
1802
+ */
1803
+ normalizeHost(host) {
1804
+ if (host.startsWith("[") && host.endsWith("]")) {
1805
+ return host.slice(1, -1);
1806
+ }
1807
+ return host;
1808
+ }
1809
+ /**
1810
+ * Format host for use in URLs.
1811
+ * Wraps IPv6 addresses in brackets per RFC 3986.
1812
+ */
1813
+ formatHostForUrl(host) {
1814
+ if (host.includes(":")) {
1815
+ return `[${host}]`;
1816
+ }
1817
+ return host;
1818
+ }
1655
1819
  normalizePath(path, withGlobalPrefix = false) {
1656
1820
  const sanitize = /* @__PURE__ */ __name((p) => {
1657
1821
  return "/" + p.trim().replace(/^\/+/, "").replace(/\/{2,}/g, "/").replace(/\/+$/, "");
package/server/index.d.ts CHANGED
@@ -363,11 +363,87 @@ declare class ExpressServer {
363
363
  * @return {*} {CatbeeServerConfig}
364
364
  */
365
365
  getConfig(): CatbeeServerConfig;
366
+ /**
367
+ * Get the port the server is listening on.
368
+ * Returns the actual port if server is running (useful when config.port was 0),
369
+ * otherwise returns the configured port.
370
+ *
371
+ * @returns The port number
372
+ */
373
+ getPort(): number;
374
+ /**
375
+ * Get the full URL the server is running on.
376
+ * Returns the actual URL if server is running (useful when config.port was 0),
377
+ * otherwise returns the configured URL.
378
+ *
379
+ * @returns The full server URL (e.g., "http://localhost:3000")
380
+ */
381
+ getUrl(): string;
382
+ /**
383
+ * Check if the server is configured for dynamic port assignment.
384
+ * Returns true if the original port configuration was 0.
385
+ *
386
+ * @returns True if using dynamic port assignment, false otherwise
387
+ */
388
+ isPortDynamic(): boolean;
389
+ /**
390
+ * Check if the server is currently running and listening for requests.
391
+ *
392
+ * @returns True if server is running, false otherwise
393
+ */
394
+ isRunning(): boolean;
395
+ /**
396
+ * Get the host address the server is bound to.
397
+ *
398
+ * @returns The host address
399
+ */
400
+ getHost(): string;
401
+ /**
402
+ * Get the protocol the server is using ('http' or 'https').
403
+ *
404
+ * @returns The protocol string
405
+ */
406
+ getProtocol(): string;
407
+ /**
408
+ * Check if the server is configured to use HTTPS.
409
+ *
410
+ * @returns True if using HTTPS, false otherwise
411
+ */
412
+ isHttps(): boolean;
413
+ /**
414
+ * Set a new port for the server.
415
+ * Can only be called before the server starts listening.
416
+ * Useful for testing scenarios where you need to change the port dynamically.
417
+ *
418
+ * @param port - The new port number (0-65535)
419
+ * @throws Error if server is already running or port is invalid
420
+ */
421
+ setPort(port: number): void;
422
+ /**
423
+ * Set a new host for the server.
424
+ * Can only be called before the server starts listening.
425
+ * Useful for testing scenarios where you need to change the host dynamically.
426
+ *
427
+ * @param host - The new host (e.g., "localhost", "0.0.0.0", "127.0.0.1", "::1", or "[::1]")
428
+ * @throws {Error} If server is already running or host is invalid
429
+ */
430
+ setHost(host: string): void;
366
431
  /**
367
432
  * Wait until server initialization (middleware + routes) has completed.
368
433
  * Useful for integration tests that inspect app before starting.
369
434
  */
370
435
  waitUntilReady(): Promise<void>;
436
+ /**
437
+ * Normalize host by stripping surrounding brackets from IPv6 addresses.
438
+ * This ensures the host value is compatible with server.listen().
439
+ * Brackets are URL syntax only and must be removed for Node.js binding.
440
+ */
441
+ private normalizeHost;
442
+ /**
443
+ * Format host for use in URLs.
444
+ * Wraps IPv6 addresses in brackets per RFC 3986.
445
+ */
446
+ private formatHostForUrl;
371
447
  private normalizePath;
372
448
  private normalizeRouteForMetrics;
373
449
  /**
@@ -403,13 +479,21 @@ declare class ServerConfigBuilder {
403
479
  *
404
480
  * @private
405
481
  * @param port - The port number to validate
406
- * @throws {Error} If port is not an integer or is outside the valid range (1-65535)
482
+ * @throws {Error} If port is not an integer or is outside the valid range (0-65535)
407
483
  */
408
484
  private validatePort;
485
+ /**
486
+ * Validates that a hostname is valid.
487
+ *
488
+ * @private
489
+ * @param host - The hostname to validate
490
+ * @throws {Error} If hostname is invalid
491
+ */
492
+ private validateHost;
409
493
  /**
410
494
  * Sets the port the server will listen on.
411
495
  *
412
- * @param port - The port number (1-65535)
496
+ * @param port - The port number (0-65535). Use 0 for dynamic port assignment.
413
497
  * @returns The builder instance for chaining
414
498
  * @throws {Error} If port is invalid
415
499
  * @default 3000 (can be overridden via PORT env variable)
@@ -425,11 +509,13 @@ declare class ServerConfigBuilder {
425
509
  *
426
510
  * @param host - The hostname (e.g., 'localhost', '0.0.0.0', '127.0.0.1')
427
511
  * @returns The builder instance for chaining
512
+ * @throws {Error} If hostname is invalid
428
513
  * @default '0.0.0.0' (can be overridden via HOST env variable)
429
514
  *
430
515
  * @example
431
516
  * ```typescript
432
517
  * builder.withHost('0.0.0.0') // Listen on all interfaces
518
+ * builder.withHost('localhost') // Listen on localhost only
433
519
  * ```
434
520
  */
435
521
  withHost(host: string): this;
@@ -768,6 +854,7 @@ declare class ServerConfigBuilder {
768
854
  * ```
769
855
  */
770
856
  withBodyParser(opts: NonNullable<CatbeeServerConfig['bodyParser']>): this;
857
+ disableBodyParser(): this;
771
858
  /**
772
859
  * Configures cookie parsing middleware.
773
860
  *