@catbee/utils 2.0.1 → 2.0.2

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.mjs CHANGED
@@ -31,9 +31,9 @@ import { Env } from '@catbee/utils/env';
31
31
  import { getLogger } from '@catbee/utils/logger';
32
32
  import { ServiceUnavailableException, InternalServerErrorException, NotFoundException } from '@catbee/utils/exception';
33
33
  import { getCatbeeServerGlobalConfig } from '@catbee/utils/config';
34
- import { deepObjMerge, deepClone } from '@catbee/utils/object';
34
+ import { deepObjMerge, isPlainObject, deepClone } from '@catbee/utils/object';
35
35
  import { fileExists, readFile, readFileSync } from '@catbee/utils/fs';
36
- import { isPort } from '@catbee/utils/validation';
36
+ import { isPort, isHostname } from '@catbee/utils/validation';
37
37
  import { optionalRequire } from '@catbee/utils/async';
38
38
  import { uuid } from '@catbee/utils/id';
39
39
 
@@ -50,17 +50,29 @@ var ServerConfigBuilder = class {
50
50
  *
51
51
  * @private
52
52
  * @param port - The port number to validate
53
- * @throws {Error} If port is not an integer or is outside the valid range (1-65535)
53
+ * @throws {Error} If port is not an integer or is outside the valid range (0-65535)
54
54
  */
55
55
  validatePort(port) {
56
- if (!isPort(port)) {
57
- throw new Error(`Port must be a valid number between 1 and 65535, got: ${port}`);
56
+ if (!isPort(port, true)) {
57
+ throw new Error(`Port must be a valid number between 0 and 65535, got: ${port}`);
58
+ }
59
+ }
60
+ /**
61
+ * Validates that a hostname is valid.
62
+ *
63
+ * @private
64
+ * @param host - The hostname to validate
65
+ * @throws {Error} If hostname is invalid
66
+ */
67
+ validateHost(host) {
68
+ if (!isHostname(host)) {
69
+ throw new Error(`Host must be a valid hostname or IP address, got: ${host}`);
58
70
  }
59
71
  }
60
72
  /**
61
73
  * Sets the port the server will listen on.
62
74
  *
63
- * @param port - The port number (1-65535)
75
+ * @param port - The port number (0-65535). Use 0 for dynamic port assignment.
64
76
  * @returns The builder instance for chaining
65
77
  * @throws {Error} If port is invalid
66
78
  * @default 3000 (can be overridden via PORT env variable)
@@ -80,14 +92,17 @@ var ServerConfigBuilder = class {
80
92
  *
81
93
  * @param host - The hostname (e.g., 'localhost', '0.0.0.0', '127.0.0.1')
82
94
  * @returns The builder instance for chaining
95
+ * @throws {Error} If hostname is invalid
83
96
  * @default '0.0.0.0' (can be overridden via HOST env variable)
84
97
  *
85
98
  * @example
86
99
  * ```typescript
87
100
  * builder.withHost('0.0.0.0') // Listen on all interfaces
101
+ * builder.withHost('localhost') // Listen on localhost only
88
102
  * ```
89
103
  */
90
104
  withHost(host) {
105
+ this.validateHost(host);
91
106
  this.config.host = host;
92
107
  return this;
93
108
  }
@@ -498,12 +513,23 @@ var ServerConfigBuilder = class {
498
513
  * ```
499
514
  */
500
515
  withBodyParser(opts) {
516
+ if (opts === true) {
517
+ this.config.bodyParser = getCatbeeServerGlobalConfig().bodyParser;
518
+ return this;
519
+ } else if (opts === false) {
520
+ this.config.bodyParser = false;
521
+ return this;
522
+ }
501
523
  this.config.bodyParser = {
502
524
  ...this.config.bodyParser,
503
525
  ...opts
504
526
  };
505
527
  return this;
506
528
  }
529
+ disableBodyParser() {
530
+ this.config.bodyParser = false;
531
+ return this;
532
+ }
507
533
  /**
508
534
  * Configures cookie parsing middleware.
509
535
  *
@@ -654,7 +680,7 @@ var ServerConfigBuilder = class {
654
680
  });
655
681
  }
656
682
  mergeConfig(key, value) {
657
- const current = this.config[key] && typeof this.config[key] === "object" ? deepClone(this.config[key]) : {};
683
+ const current = isPlainObject(this.config[key]) ? deepClone(this.config[key]) : {};
658
684
  this.config[key] = deepObjMerge({}, current, value);
659
685
  }
660
686
  setEnabled(key, enable, overrides = {}) {
@@ -735,12 +761,15 @@ var ExpressServer = class {
735
761
  */
736
762
  constructor(config, hooks = {}) {
737
763
  if (this.hasBuildMarker(config)) {
738
- this.config = config;
764
+ this.config = deepObjMerge({}, config);
739
765
  } else {
740
766
  this.config = deepObjMerge({}, getCatbeeServerGlobalConfig(), config);
741
767
  }
742
- if (!isPort(this.config.port)) {
743
- const msg = `Port must be a valid number between 1 and 65535, got: ${this.config.port}`;
768
+ if (this.config.host) {
769
+ this.config.host = this.normalizeHost(this.config.host);
770
+ }
771
+ if (!isPort(this.config.port, true)) {
772
+ const msg = `Port must be a valid number between 0 and 65535, got: ${this.config.port}`;
744
773
  getLogger().error(msg);
745
774
  throw new Error(msg);
746
775
  }
@@ -1019,7 +1048,6 @@ var ExpressServer = class {
1019
1048
  }
1020
1049
  const logger = getLogger();
1021
1050
  const incomingRequestMetaData = {
1022
- requestId: req.id,
1023
1051
  method: req.method,
1024
1052
  url: req.originalUrl || req.url,
1025
1053
  ip: req.ip
@@ -1069,13 +1097,23 @@ var ExpressServer = class {
1069
1097
  * Set up body parsing middleware.
1070
1098
  */
1071
1099
  setupBodyParsingMiddleware() {
1072
- if (this.config.bodyParser) {
1100
+ if (isPlainObject(this.config.bodyParser)) {
1073
1101
  if (this.config.bodyParser.json) {
1074
1102
  this.app.use(express.json(this.config.bodyParser.json));
1075
1103
  }
1076
1104
  if (this.config.bodyParser.urlencoded) {
1077
1105
  this.app.use(express.urlencoded(this.config.bodyParser.urlencoded));
1078
1106
  }
1107
+ } else if (this.config.bodyParser === true) {
1108
+ const globalBodyParserConfig = getCatbeeServerGlobalConfig().bodyParser;
1109
+ if (isPlainObject(globalBodyParserConfig)) {
1110
+ if (globalBodyParserConfig.json) {
1111
+ this.app.use(express.json(globalBodyParserConfig.json));
1112
+ }
1113
+ if (globalBodyParserConfig.urlencoded) {
1114
+ this.app.use(express.urlencoded(globalBodyParserConfig.urlencoded));
1115
+ }
1116
+ }
1079
1117
  }
1080
1118
  }
1081
1119
  /**
@@ -1414,7 +1452,9 @@ var ExpressServer = class {
1414
1452
  */
1415
1453
  logServerStartInfo() {
1416
1454
  const protocol = this.config.https ? "https" : "http";
1417
- const url = `${protocol}://${this.config.host}:${this.config.port}`;
1455
+ const port = this.getPort();
1456
+ const host = this.formatHostForUrl(this.config.host || "localhost");
1457
+ const url = `${protocol}://${host}:${port}`;
1418
1458
  getLogger().info(`Server running on ${url}`);
1419
1459
  if (this.config.healthCheck?.path) {
1420
1460
  getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
@@ -1639,12 +1679,136 @@ var ExpressServer = class {
1639
1679
  return this.config;
1640
1680
  }
1641
1681
  /**
1682
+ * Get the port the server is listening on.
1683
+ * Returns the actual port if server is running (useful when config.port was 0),
1684
+ * otherwise returns the configured port.
1685
+ *
1686
+ * @returns The port number
1687
+ */
1688
+ getPort() {
1689
+ const address = this.server?.address();
1690
+ if (address && typeof address === "object" && "port" in address) {
1691
+ return address.port;
1692
+ }
1693
+ return this.config.port;
1694
+ }
1695
+ /**
1696
+ * Get the full URL the server is running on.
1697
+ * Returns the actual URL if server is running (useful when config.port was 0),
1698
+ * otherwise returns the configured URL.
1699
+ *
1700
+ * @returns The full server URL (e.g., "http://localhost:3000")
1701
+ */
1702
+ getUrl() {
1703
+ const protocol = this.config.https ? "https" : "http";
1704
+ const port = this.getPort();
1705
+ const host = this.formatHostForUrl(this.config.host || "localhost");
1706
+ return `${protocol}://${host}:${port}`;
1707
+ }
1708
+ /**
1709
+ * Check if the server is configured for dynamic port assignment.
1710
+ * Returns true if the original port configuration was 0.
1711
+ *
1712
+ * @returns True if using dynamic port assignment, false otherwise
1713
+ */
1714
+ isPortDynamic() {
1715
+ return this.config.port === 0;
1716
+ }
1717
+ /**
1718
+ * Check if the server is currently running and listening for requests.
1719
+ *
1720
+ * @returns True if server is running, false otherwise
1721
+ */
1722
+ isRunning() {
1723
+ return this.server !== null && this.server.listening;
1724
+ }
1725
+ /**
1726
+ * Get the host address the server is bound to.
1727
+ *
1728
+ * @returns The host address
1729
+ */
1730
+ getHost() {
1731
+ return this.config.host || "localhost";
1732
+ }
1733
+ /**
1734
+ * Get the protocol the server is using ('http' or 'https').
1735
+ *
1736
+ * @returns The protocol string
1737
+ */
1738
+ getProtocol() {
1739
+ return this.config.https ? "https" : "http";
1740
+ }
1741
+ /**
1742
+ * Check if the server is configured to use HTTPS.
1743
+ *
1744
+ * @returns True if using HTTPS, false otherwise
1745
+ */
1746
+ isHttps() {
1747
+ return this.config.https !== void 0;
1748
+ }
1749
+ /**
1750
+ * Set a new port for the server.
1751
+ * Can only be called before the server starts listening.
1752
+ * Useful for testing scenarios where you need to change the port dynamically.
1753
+ *
1754
+ * @param port - The new port number (0-65535)
1755
+ * @throws Error if server is already running or port is invalid
1756
+ */
1757
+ setPort(port) {
1758
+ if (this.server) {
1759
+ throw new Error("Cannot change port after server has started");
1760
+ }
1761
+ if (!isPort(port, true)) {
1762
+ throw new Error(`Port must be a valid number between 0 and 65535, got: ${port}`);
1763
+ }
1764
+ this.config.port = port;
1765
+ }
1766
+ /**
1767
+ * Set a new host for the server.
1768
+ * Can only be called before the server starts listening.
1769
+ * Useful for testing scenarios where you need to change the host dynamically.
1770
+ *
1771
+ * @param host - The new host (e.g., "localhost", "0.0.0.0", "127.0.0.1", "::1", or "[::1]")
1772
+ * @throws {Error} If server is already running or host is invalid
1773
+ */
1774
+ setHost(host) {
1775
+ if (this.server) {
1776
+ throw new Error("Cannot change host after server has started");
1777
+ }
1778
+ const normalizedHost = this.normalizeHost(host);
1779
+ if (!isHostname(normalizedHost)) {
1780
+ throw new Error(`Host must be a valid hostname or IP address, got: ${host}`);
1781
+ }
1782
+ this.config.host = normalizedHost;
1783
+ }
1784
+ /**
1642
1785
  * Wait until server initialization (middleware + routes) has completed.
1643
1786
  * Useful for integration tests that inspect app before starting.
1644
1787
  */
1645
1788
  async waitUntilReady() {
1646
1789
  await this.initPromise;
1647
1790
  }
1791
+ /**
1792
+ * Normalize host by stripping surrounding brackets from IPv6 addresses.
1793
+ * This ensures the host value is compatible with server.listen().
1794
+ * Brackets are URL syntax only and must be removed for Node.js binding.
1795
+ */
1796
+ normalizeHost(host) {
1797
+ if (host.startsWith("[") && host.endsWith("]")) {
1798
+ return host.slice(1, -1);
1799
+ }
1800
+ return host;
1801
+ }
1802
+ /**
1803
+ * Format host for use in URLs.
1804
+ * Wraps IPv6 addresses in brackets per RFC 3986.
1805
+ */
1806
+ formatHostForUrl(host) {
1807
+ if (host.includes(":")) {
1808
+ return `[${host}]`;
1809
+ }
1810
+ return host;
1811
+ }
1648
1812
  normalizePath(path, withGlobalPrefix = false) {
1649
1813
  const sanitize = /* @__PURE__ */ __name((p) => {
1650
1814
  return "/" + p.trim().replace(/^\/+/, "").replace(/\/{2,}/g, "/").replace(/\/+$/, "");
package/types/index.d.ts CHANGED
@@ -166,6 +166,7 @@ interface CatbeeServerConfig {
166
166
  /** Server port
167
167
  * - **default**: `3000`
168
168
  * - **env**: `SERVER_PORT` || `PORT`
169
+ * - Use `0` for dynamic port assignment (OS assigns available port)
169
170
  */
170
171
  port: number;
171
172
  /** Host address to bind the server
@@ -226,7 +227,7 @@ interface CatbeeServerConfig {
226
227
  * - `SERVER_BODY_PARSER_JSON_LIMIT`
227
228
  * - `SERVER_BODY_PARSER_URLENCODED_LIMIT`
228
229
  */
229
- bodyParser?: {
230
+ bodyParser?: ToggleConfig<{
230
231
  /** JSON body parser options
231
232
  * - **default**: `{ limit: '1mb' }`
232
233
  */
@@ -235,7 +236,7 @@ interface CatbeeServerConfig {
235
236
  * - **default**: `{ extended: true, limit: '1mb' }`
236
237
  */
237
238
  urlencoded?: Parameters<typeof urlencoded>[0];
238
- };
239
+ }>;
239
240
  /** Cookie parser toggle or options
240
241
  * - **default**: `false`
241
242
  * - **env**: `SERVER_COOKIE_PARSER_ENABLE`
@@ -42,13 +42,15 @@ var BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}
42
42
  var DOT_ATOM_REGEX = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
43
43
  var QUOTED_LOCAL_REGEX = /^"([\s\x21\x23-\x5B\x5D-\x7E]|\\[\x20-\x7E])*"$/;
44
44
  var TLD_REGEX = /^[A-Za-z]{2,63}$/;
45
- function isPort(value) {
46
- if (typeof value === "number") return Number.isInteger(value) && value > 0 && value <= 65535;
45
+ function isPort(value, allowZero = false) {
46
+ const minPort = allowZero ? 0 : 1;
47
+ const maxPort = 65535;
48
+ if (typeof value === "number") return Number.isInteger(value) && value >= minPort && value <= maxPort;
47
49
  if (!value || typeof value !== "string") return false;
48
50
  const str = value.trim();
49
51
  if (!PORT_REGEX.test(str)) return false;
50
52
  const port = Number(str);
51
- return Number.isInteger(port) && port > 0 && port <= 65535;
53
+ return Number.isInteger(port) && port >= minPort && port <= maxPort;
52
54
  }
53
55
  __name(isPort, "isPort");
54
56
  function isEmail(str) {
@@ -154,7 +156,7 @@ function isIPv4(str) {
154
156
  const parts = str.split(".");
155
157
  if (parts.length !== 4) return false;
156
158
  return parts.every((p) => {
157
- if (p.length > 3 || p.startsWith("0") && p.length > 1) return false;
159
+ if (!p || p.length > 3 || p.startsWith("0") && p.length > 1) return false;
158
160
  const n = Number(p);
159
161
  return Number.isInteger(n) && n >= 0 && n <= 255;
160
162
  });
@@ -169,6 +171,29 @@ function isIPv6(str) {
169
171
  }
170
172
  }
171
173
  __name(isIPv6, "isIPv6");
174
+ function isHostname(str) {
175
+ if (!str || typeof str !== "string") return false;
176
+ let input = str.trim();
177
+ if (!input || input.length > 255) return false;
178
+ if (input.endsWith(".")) {
179
+ input = input.slice(0, -1);
180
+ }
181
+ if (isIPv4(input)) return true;
182
+ if (isIPv6(input)) return true;
183
+ if (input === "localhost") return true;
184
+ if (/^\d+(\.\d+){3,}$/.test(input)) {
185
+ return false;
186
+ }
187
+ if (input.includes("..")) return false;
188
+ const labels = input.split(".");
189
+ if (labels.length === 0) return false;
190
+ const labelRegex = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i;
191
+ return labels.every((label) => {
192
+ if (!label || label.length > 63) return false;
193
+ return labelRegex.test(label);
194
+ });
195
+ }
196
+ __name(isHostname, "isHostname");
172
197
  function isCreditCard(str) {
173
198
  if (!str || typeof str !== "string") return false;
174
199
  const s = str.replace(/\D/g, "");
@@ -243,6 +268,7 @@ exports.isCreditCard = isCreditCard;
243
268
  exports.isDateInRange = isDateInRange;
244
269
  exports.isEmail = isEmail;
245
270
  exports.isHexColor = isHexColor;
271
+ exports.isHostname = isHostname;
246
272
  exports.isIPv4 = isIPv4;
247
273
  exports.isIPv6 = isIPv6;
248
274
  exports.isISODate = isISODate;
@@ -28,10 +28,11 @@
28
28
  /**
29
29
  * Checks if a string is a valid port number.
30
30
  *
31
- * @param str - The input string or number.
31
+ * @param value - The input string or number.
32
+ * @param allowZero - Whether to allow zero as a valid port number.
32
33
  * @returns True if valid port number, else false.
33
34
  */
34
- declare function isPort(value: string | number): boolean;
35
+ declare function isPort(value: string | number, allowZero?: boolean): boolean;
35
36
  /**
36
37
  * Checks if a string is a valid email address.
37
38
  *
@@ -142,6 +143,31 @@ declare function isIPv4(str: string): boolean;
142
143
  * @returns {boolean} True if valid IPv6 address.
143
144
  */
144
145
  declare function isIPv6(str: string): boolean;
146
+ /**
147
+ * Checks if a string is a valid hostname or IP address.
148
+ *
149
+ * Validates:
150
+ * - IPv4 addresses (e.g., '192.168.1.1')
151
+ * - IPv6 addresses (e.g., '::1', '2001:db8::1')
152
+ * - Domain names (e.g., 'example.com', 'sub.example.com')
153
+ * - Localhost variants ('localhost', '0.0.0.0', '127.0.0.1')
154
+ *
155
+ * @param {string} str - The hostname or IP address to validate.
156
+ * @returns {boolean} True if valid hostname or IP address.
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * isHostname('localhost'); // true
161
+ * isHostname('0.0.0.0'); // true
162
+ * isHostname('192.168.1.1'); // true
163
+ * isHostname('example.com'); // true
164
+ * isHostname('sub.example.com'); // true
165
+ * isHostname('::1'); // true
166
+ * isHostname('invalid..com'); // false
167
+ * isHostname(''); // false
168
+ * ```
169
+ */
170
+ declare function isHostname(str: string): boolean;
145
171
  /**
146
172
  * Validates a credit card number using the Luhn algorithm.
147
173
  *
@@ -206,4 +232,4 @@ declare function matchesPattern(str: string, pattern: RegExp): boolean;
206
232
  */
207
233
  declare function validateAll(value: unknown, validators: Array<(value: unknown) => boolean>): boolean;
208
234
 
209
- export { hasRequiredProps, isAlpha, isAlphanumeric, isArray, isBase64, isCreditCard, isDateInRange, isEmail, isHexColor, isIPv4, isIPv6, isISODate, isLengthBetween, isNumberBetween, isNumeric, isPhone, isPort, isStrongPassword, isURL, isUUID, isValidJSON, matchesPattern, validateAll };
235
+ export { hasRequiredProps, isAlpha, isAlphanumeric, isArray, isBase64, isCreditCard, isDateInRange, isEmail, isHexColor, isHostname, isIPv4, isIPv6, isISODate, isLengthBetween, isNumberBetween, isNumeric, isPhone, isPort, isStrongPassword, isURL, isUUID, isValidJSON, matchesPattern, validateAll };
@@ -36,13 +36,15 @@ var BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}
36
36
  var DOT_ATOM_REGEX = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
37
37
  var QUOTED_LOCAL_REGEX = /^"([\s\x21\x23-\x5B\x5D-\x7E]|\\[\x20-\x7E])*"$/;
38
38
  var TLD_REGEX = /^[A-Za-z]{2,63}$/;
39
- function isPort(value) {
40
- if (typeof value === "number") return Number.isInteger(value) && value > 0 && value <= 65535;
39
+ function isPort(value, allowZero = false) {
40
+ const minPort = allowZero ? 0 : 1;
41
+ const maxPort = 65535;
42
+ if (typeof value === "number") return Number.isInteger(value) && value >= minPort && value <= maxPort;
41
43
  if (!value || typeof value !== "string") return false;
42
44
  const str = value.trim();
43
45
  if (!PORT_REGEX.test(str)) return false;
44
46
  const port = Number(str);
45
- return Number.isInteger(port) && port > 0 && port <= 65535;
47
+ return Number.isInteger(port) && port >= minPort && port <= maxPort;
46
48
  }
47
49
  __name(isPort, "isPort");
48
50
  function isEmail(str) {
@@ -148,7 +150,7 @@ function isIPv4(str) {
148
150
  const parts = str.split(".");
149
151
  if (parts.length !== 4) return false;
150
152
  return parts.every((p) => {
151
- if (p.length > 3 || p.startsWith("0") && p.length > 1) return false;
153
+ if (!p || p.length > 3 || p.startsWith("0") && p.length > 1) return false;
152
154
  const n = Number(p);
153
155
  return Number.isInteger(n) && n >= 0 && n <= 255;
154
156
  });
@@ -163,6 +165,29 @@ function isIPv6(str) {
163
165
  }
164
166
  }
165
167
  __name(isIPv6, "isIPv6");
168
+ function isHostname(str) {
169
+ if (!str || typeof str !== "string") return false;
170
+ let input = str.trim();
171
+ if (!input || input.length > 255) return false;
172
+ if (input.endsWith(".")) {
173
+ input = input.slice(0, -1);
174
+ }
175
+ if (isIPv4(input)) return true;
176
+ if (isIPv6(input)) return true;
177
+ if (input === "localhost") return true;
178
+ if (/^\d+(\.\d+){3,}$/.test(input)) {
179
+ return false;
180
+ }
181
+ if (input.includes("..")) return false;
182
+ const labels = input.split(".");
183
+ if (labels.length === 0) return false;
184
+ const labelRegex = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i;
185
+ return labels.every((label) => {
186
+ if (!label || label.length > 63) return false;
187
+ return labelRegex.test(label);
188
+ });
189
+ }
190
+ __name(isHostname, "isHostname");
166
191
  function isCreditCard(str) {
167
192
  if (!str || typeof str !== "string") return false;
168
193
  const s = str.replace(/\D/g, "");
@@ -228,4 +253,4 @@ function validateAll(value, validators) {
228
253
  }
229
254
  __name(validateAll, "validateAll");
230
255
 
231
- export { hasRequiredProps, isAlpha, isAlphanumeric, isArray, isBase64, isCreditCard, isDateInRange, isEmail, isHexColor, isIPv4, isIPv6, isISODate, isLengthBetween, isNumberBetween, isNumeric, isPhone, isPort, isStrongPassword, isURL, isUUID, isValidJSON, matchesPattern, validateAll };
256
+ export { hasRequiredProps, isAlpha, isAlphanumeric, isArray, isBase64, isCreditCard, isDateInRange, isEmail, isHexColor, isHostname, isIPv4, isIPv6, isISODate, isLengthBetween, isNumberBetween, isNumeric, isPhone, isPort, isStrongPassword, isURL, isUUID, isValidJSON, matchesPattern, validateAll };