@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/crypto/index.cjs +288 -0
- package/crypto/index.d.ts +230 -3
- package/crypto/index.mjs +283 -2
- package/date/index.cjs +343 -102
- package/date/index.d.ts +178 -6
- package/date/index.mjs +338 -103
- package/logger/index.cjs +1 -1
- package/logger/index.mjs +1 -1
- package/package.json +6 -6
- package/server/index.cjs +175 -11
- package/server/index.d.ts +89 -2
- package/server/index.mjs +177 -13
- package/types/index.d.ts +3 -2
- package/validation/index.cjs +30 -4
- package/validation/index.d.ts +29 -3
- package/validation/index.mjs +30 -5
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 (
|
|
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
|
|
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 (
|
|
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]
|
|
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 (
|
|
743
|
-
|
|
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
|
|
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`
|
package/validation/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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;
|
package/validation/index.d.ts
CHANGED
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
/**
|
|
29
29
|
* Checks if a string is a valid port number.
|
|
30
30
|
*
|
|
31
|
-
* @param
|
|
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 };
|
package/validation/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 };
|