Skip to content

Value parsers

Value parsers are the specialized components responsible for converting raw string input from command-line arguments into strongly-typed values. While command-line arguments are always strings, your application needs them as numbers, URLs, file paths, or other typed values. Value parsers handle this critical transformation and provide validation at parse time.

The philosophy behind Optique's value parsers is “fail fast, fail clearly.” Rather than letting invalid data flow through your application and cause mysterious errors later, value parsers validate input immediately when parsing occurs. When validation fails, they provide clear, actionable error messages that help users understand what went wrong and how to fix it.

Every value parser implements the ValueParser<T> interface, which defines how to parse strings into values of type T and how to format those values back into strings for help text. This consistent interface makes value parsers composable and allows you to create custom parsers that integrate seamlessly with Optique's type system.

string() parser

The string() parser is the most basic value parser—it accepts any string input and performs optional pattern validation. While all command-line arguments start as strings, the string() parser provides a way to explicitly document that you want string values and optionally validate their format.

import { 
string
} from "@optique/core/valueparser";
// Basic string parser const
name
=
string
();
// String with custom metavar for help text const
hostname
=
string
({
metavar
: "HOST" });
// String with pattern validation const
identifier
=
string
({
metavar
: "ID",
pattern
: /^[a-zA-Z][a-zA-Z0-9_]*$/
});

Pattern validation

The optional pattern parameter accepts a regular expression that the input must match:

// Email-like pattern
const 
email
=
string
({
pattern
: /^[^@]+@[^@]+\.[^@]+$/,
metavar
: "EMAIL"
}); // Semantic version pattern const
version
=
string
({
pattern
: /^\d+\.\d+\.\d+$/,
metavar
: "VERSION"
});

When pattern validation fails, the parser provides a clear error message indicating what pattern was expected:

$ myapp --version 1.2
Error: Expected a string matching pattern ^\d+\.\d+\.\d+$, but got 1.2.

The string() parser uses "STRING" as its default metavar, which appears in help text to indicate what kind of input is expected.

integer() parser

The integer() parser converts string input to numeric values with validation. It supports both regular JavaScript numbers (safe up to Number.MAX_SAFE_INTEGER) and arbitrary-precision bigint values for very large integers.

Number mode (default)

By default, integer() returns JavaScript numbers and validates that input contains only digits:

import { 
integer
} from "@optique/core/valueparser";
// Basic integer const
count
=
integer
();
// Integer with bounds checking const
port
=
integer
({
min
: 1,
max
: 0xffff });
// Integer with custom metavar const
timeout
=
integer
({
min
: 0,
metavar
: "SECONDS" });

Bigint mode

For very large integers that exceed JavaScript's safe integer range, specify type: "bigint":

// BigInt integer with bounds
const 
largeNumber
=
integer
({
type
: "bigint",
min
: 0n,
max
: 999999999999999999999n
});

Validation and error messages

The integer() parser provides detailed validation:

  • Format validation: Ensures input contains only digits
  • Range validation: Enforces minimum and maximum bounds
  • Overflow protection: Prevents values outside safe ranges
$ myapp --port "abc"
Error: Expected a valid integer, but got abc.

$ myapp --port "99999"
Error: Expected a value less than or equal to 65,535, but got 99999.

The parser uses "INTEGER" as its default metavar.

float() parser

The float() parser handles floating-point numbers with comprehensive validation options. It recognizes standard decimal notation, scientific notation, and optionally special values like NaN and Infinity.

import { 
float
} from "@optique/core/valueparser";
// Basic float const
rate
=
float
();
// Float with bounds const
percentage
=
float
({
min
: 0.0,
max
: 100.0 });
// Float allowing special values const
scientific
=
float
({
allowNaN
: true,
allowInfinity
: true,
metavar
: "NUMBER"
});

Supported formats

The float() parser recognizes multiple numeric formats:

  • Integers: 42, -17
  • Decimals: 3.14, -2.5, .5, 42.
  • Scientific notation: 1.23e10, 5.67E-4, 1e+5
  • Special values: NaN, Infinity, -Infinity (when allowed)

Special values

By default, NaN and Infinity are not allowed, which prevents accidental acceptance of these values. Enable them explicitly when they're meaningful for your use case:

// Mathematics application that handles infinite limits
const 
limit
=
float
({
allowInfinity
: true,
metavar
: "LIMIT"
}); // Statistical application that handles missing data const
value
=
float
({
allowNaN
: true,
allowInfinity
: true,
metavar
: "VALUE"
});

The parser uses "NUMBER" as its default metavar and provides clear error messages for invalid formats and out-of-range values.

choice() parser

The choice() parser creates type-safe enumerations by restricting input to one of several predefined string values. This is perfect for options like log levels, output formats, or operation modes where only specific values make sense.

import { 
choice
} from "@optique/core/valueparser";
// Log level choice const
logLevel
=
choice
(["debug", "info", "warn", "error"]);
// Output format choice const
format
=
choice
(["json", "yaml", "xml", "csv"]);
// Case-insensitive choice const
protocol
=
choice
(["http", "https", "ftp"], {
caseInsensitive
: true
});

Type-safe string literals

The choice() parser creates exact string literal types rather than generic string types, providing excellent TypeScript integration:

const 
level
=
choice
(["debug", "info", "warn", "error"]);

Case sensitivity

By default, matching is case-sensitive. Set caseInsensitive: true to accept variations:

const 
format
=
choice
(["JSON", "XML"], {
caseInsensitive
: true,
metavar
: "FORMAT"
}); // Accepts: "json", "JSON", "Json", "xml", "XML", "Xml"

Error messages

When invalid choices are provided, the parser lists all valid options:

$ myapp --format "txt"
Error: Expected one of json, yaml, xml, csv, but got txt.

The parser uses "TYPE" as its default metavar.

Number choices

This feature is available since Optique 0.9.0.

The choice() parser also supports number literals, which is useful for options like bit depths, port numbers, or other numeric values where only specific values are valid:

import { 
choice
} from "@optique/core/valueparser";
// Bit depth choice const
bitDepth
=
choice
([8, 10, 12]);
// Port selection const
port
=
choice
([80, 443, 8080]);

Number choices provide the same type-safe literal types as string choices:

const 
depth
=
choice
([8, 10, 12]);

NOTE

The caseInsensitive option is only available for string choices. TypeScript will report an error if you try to use it with number choices.

url() parser

The url() parser validates that input is a well-formed URL and optionally restricts the allowed protocols. The parsed result is a JavaScript URL object with all the benefits of the native URL API.

import { 
url
} from "@optique/core/valueparser";
// Basic URL parser const
endpoint
=
url
();
// URL with protocol restrictions const
apiUrl
=
url
({
allowedProtocols
: ["https:"],
metavar
: "HTTPS_URL"
}); // URL allowing multiple protocols const
downloadUrl
=
url
({
allowedProtocols
: ["http:", "https:", "ftp:"],
metavar
: "URL"
});

Protocol validation

The allowedProtocols option restricts which URL schemes are acceptable. Protocol names must include the trailing colon:

// Only secure protocols
const 
secureUrl
=
url
({
allowedProtocols
: ["https:", "wss:"]
}); // Web protocols only const
webUrl
=
url
({
allowedProtocols
: ["http:", "https:"]
});

URL object benefits

The parser returns native URL objects, providing immediate access to URL components:

const 
result
=
parse
(
apiUrl
, ["https://api.example.com:8080/v1/users"]);
if (
result
.
success
) {
const
url
=
result
.
value
;
console
.
log
(`Host: ${
url
.
hostname
}`); // "api.example.com"
console
.
log
(`Port: ${
url
.
port
}`); // "8080"
console
.
log
(`Path: ${
url
.
pathname
}`); // "/v1/users"
console
.
log
(`Protocol: ${
url
.
protocol
}`); // "https:"
}

Validation errors

The parser provides specific error messages for different validation failures:

$ myapp --url "not-a-url"
Error: Invalid URL: not-a-url.

$ myapp --url "ftp://example.com"  # when only HTTPS allowed
Error: URL protocol ftp: is not allowed. Allowed protocols: https:.

The parser uses "URL" as its default metavar.

locale() parser

The locale() parser validates locale identifiers according to the Unicode Locale Identifier standard (BCP 47). It returns Intl.Locale objects that provide rich locale information and integrate with JavaScript's internationalization APIs.

import { 
locale
} from "@optique/core/valueparser";
// Basic locale parser const
userLocale
=
locale
();
// Locale with custom metavar const
displayLocale
=
locale
({
metavar
: "LANG" });

Locale validation

The parser uses the native Intl.Locale constructor for validation, ensuring compliance with international standards:

// Valid locales
const validLocales = [
  "en",              // Language only
  "en-US",           // Language and region
  "zh-Hans-CN",      // Language, script, and region
  "de-DE-u-co-phonebk" // With Unicode extension
];

// Invalid locales will be rejected
const invalidLocales = [
  "invalid-locale",
  "en_US",           // Underscore instead of hyphen
  "toolong-language-tag-name"
];

Locale object benefits

The parser returns Intl.Locale objects with rich locale information:

const 
result
=
parse
(
userLocale
, ["zh-Hans-CN"]);
if (
result
.
success
) {
const
locale
=
result
.
value
;
console
.
log
(`Language: ${
locale
.
language
}`); // "zh"
console
.
log
(`Script: ${
locale
.
script
}`); // "Hans"
console
.
log
(`Region: ${
locale
.
region
}`); // "CN"
console
.
log
(`Base name: ${
locale
.
baseName
}`); // "zh-Hans-CN"
}

The parser uses "LOCALE" as its default metavar and provides clear error messages for invalid locale identifiers.

uuid() parser

The uuid() parser validates UUID (Universally Unique Identifier) strings according to the standard format and optionally restricts to specific UUID versions. It returns a branded Uuid string type for additional type safety.

import { 
uuid
} from "@optique/core/valueparser";
// Basic UUID parser const
id
=
uuid
();
// UUID with version restrictions const
uuidV4
=
uuid
({
allowedVersions
: [4],
metavar
: "UUID_V4"
}); // UUID allowing multiple versions const
trackingId
=
uuid
({
allowedVersions
: [1, 4, 5]
});

UUID format validation

The parser validates the standard UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx where each x is a hexadecimal digit:

# Valid UUID formats
550e8400-e29b-41d4-a716-446655440000  # Version 1
123e4567-e89b-12d3-a456-426614174000  # Version 1
6ba7b810-9dad-11d1-80b4-00c04fd430c8  # Version 1
6ba7b811-9dad-11d1-80b4-00c04fd430c8  # Version 1

# Invalid formats
550e8400-e29b-41d4-a716-44665544000   # Too short
550e8400-e29b-41d4-a716-446655440000x  # Extra character
550e8400e29b41d4a716446655440000       # Missing hyphens

Version validation

When allowedVersions is specified, the parser checks the version digit (character 14) and validates it matches one of the allowed versions:

const 
uuidV4Only
=
uuid
({
allowedVersions
: [4] });
// This will pass validation (version 4 UUID) const
validV4
= "550e8400-e29b-41d4-a716-446655440000";
// This will fail validation (version 1 UUID) const
invalidV1
= "6ba7b810-9dad-11d1-80b4-00c04fd430c8";

UUID type safety

The parser returns a branded Uuid type rather than a plain string, providing additional compile-time safety:

type 
Uuid
= `${string}-${string}-${string}-${string}-${string}`;
// The branded type prevents accidental usage of regular strings as UUIDs function
processUuid
(
id
:
Uuid
) {
// Implementation here } const
result
=
parse
(
uuidParser
, ["550e8400-e29b-41d4-a716-446655440000"]);
if (
result
.
success
) {
processUuid
(
result
.
value
); // ✓ Type-safe
} // This would cause a TypeScript error:
processUuid
("not-a-uuid"); // ✗ Type error
Argument of type '"not-a-uuid"' is not assignable to parameter of type '`${string}-${string}-${string}-${string}-${string}`'.

The parser uses "UUID" as its default metavar and provides specific error messages for format violations and version mismatches.

port() parser

The port() parser validates TCP/UDP port numbers with support for both number and bigint types, range validation, and well-known port restrictions. Port numbers are commonly used in network applications for server addresses, database connections, and service configurations.

import { 
port
} from "@optique/core/valueparser";
// Basic port parser (1-65535) const
serverPort
=
port
();
// Non-privileged ports only (1024-65535) const
userPort
=
port
({
min
: 1024,
max
: 65535 });
// Development ports const
devPort
=
port
({
min
: 3000,
max
: 9000 });
// Disallow well-known ports (1-1023) const
appPort
=
port
({
disallowWellKnown
: true });

Port number validation

By default, the port() parser validates that the input is a valid port number between 1 and 65535 (the full range of valid TCP/UDP ports). You can customize this range using the min and max options:

// Web server ports (typically 80 or 443)
const 
webPort
=
port
({
min
: 1,
max
: 65535 });
// Custom application ports const
customPort
=
port
({
min
: 8000,
max
: 8999 });

Well-known port restrictions

The disallowWellKnown option rejects ports 1–1023, which typically require elevated privileges (root/administrator) to bind on most systems. This is useful for applications that should run without special permissions:

const 
unprivilegedPort
=
port
({
disallowWellKnown
: true,
metavar
: "PORT"
});

When a well-known port is rejected, the error message explains why:

$ myapp --port 80
Error: Port 80 is a well-known port (1-1023) and may require elevated privileges.

Number and bigint modes

Like the integer() parser, port() supports both number (default) and bigint types. While all valid port numbers fit safely in JavaScript's number type, the bigint option is provided for consistency with other numeric parsers:

// Default: returns number
const 
numPort
=
port
();
// Bigint mode: returns bigint const
bigintPort
=
port
({
type
: "bigint" });

Common use cases

The port() parser is commonly used in network applications:

// HTTP server
const 
httpPort
=
option
("--port",
port
({
min
: 1024 }));
// Database connection const
dbPort
=
option
("--db-port",
port
());
// Redis connection (default port 6379) const
redisPort
=
option
("--redis-port",
port
());

The parser uses "PORT" as its default metavar and provides specific error messages for invalid port numbers, range violations, and well-known port restrictions.

ipv4() parser

The ipv4() parser validates IPv4 addresses in dotted-decimal notation with comprehensive filtering options for different IP address types. It's commonly used for network configuration, server addresses, and IP allowlists/blocklists.

import { 
ipv4
} from "@optique/core/valueparser";
// Basic IPv4 parser (allows all types) const
address
=
ipv4
();
// Public IPs only (no private/loopback) const
publicIp
=
ipv4
({
allowPrivate
: false,
allowLoopback
: false
}); // Server binding (allow 0.0.0.0 and private IPs) const
bindAddress
=
ipv4
({
allowZero
: true,
allowPrivate
: true
});

IPv4 format validation

The ipv4() parser validates that input matches the standard IPv4 format: four decimal octets (0-255) separated by dots (e.g., “192.168.1.1”). Each octet must be in the valid range, and the parser strictly rejects:

  • Leading zeros: “192.168.001.1” is invalid (except single “0”)
  • Whitespace: Leading, trailing, or embedded spaces are rejected
  • Empty octets: “192.168..1” is invalid
  • Out-of-range values: Octets must be 0-255
const 
parser
=
ipv4
();
// Valid IPv4 addresses
parser
.
parse
("192.168.1.1"); // ✓
parser
.
parse
("10.0.0.1"); // ✓
parser
.
parse
("255.255.255.255"); // ✓
parser
.
parse
("0.0.0.0"); // ✓
// Invalid formats
parser
.
parse
("192.168.001.1"); // ✗ Leading zero
parser
.
parse
("256.1.1.1"); // ✗ Octet > 255
parser
.
parse
("192.168.1"); // ✗ Only 3 octets
parser
.
parse
("192.168..1"); // ✗ Empty octet

IP address filtering

The parser provides fine-grained control over which IP address types are accepted through boolean filter options. All filters default to true (permissive), allowing you to selectively restrict specific address types:

allowPrivate
Controls private IP ranges (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Set to false to reject these addresses.
allowLoopback
Controls loopback addresses (127.0.0.0/8). Set to false to reject addresses like 127.0.0.1.
allowLinkLocal
Controls link-local addresses (169.254.0.0/16). Set to false to reject APIPA/link-local addresses.
allowMulticast
Controls multicast addresses (224.0.0.0/4). Set to false to reject addresses in the 224-239 range.
allowBroadcast
Controls the broadcast address (255.255.255.255). Set to false to reject the all-hosts broadcast address.
allowZero
Controls the zero address (0.0.0.0). Set to false to reject the “any” or “unspecified” address.
// Public-facing API endpoint (no private/loopback IPs)
const 
publicEndpoint
=
option
("--api-endpoint",
ipv4
({
allowPrivate
: false,
allowLoopback
: false,
allowLinkLocal
: false
})); // Server binding address (allow 0.0.0.0 and private IPs) const
bindAddr
=
option
("--bind",
ipv4
({
allowZero
: true,
allowPrivate
: true
})); // Client IP address (no special addresses) const
clientIp
=
option
("--client-ip",
ipv4
({
allowZero
: false,
allowBroadcast
: false,
allowMulticast
: false
}));

Error messages

The parser provides specific error messages for different validation failures:

$ myapp --ip "192.168.001.1"
Error: Expected a valid IPv4 address, but got 192.168.001.1.

$ myapp --public-ip "192.168.1.1"  # when private IPs are disallowed
Error: 192.168.1.1 is a private IP address.

$ myapp --endpoint "127.0.0.1"  # when loopback is disallowed
Error: 127.0.0.1 is a loopback address.

$ myapp --bind "255.255.255.255"  # when broadcast is disallowed
Error: 255.255.255.255 is the broadcast address.

Common use cases

The ipv4() parser is commonly used in network applications:

// HTTP server configuration
const 
serverConfig
=
object
({
bind
:
option
("--bind",
ipv4
({
allowPrivate
: true })),
port
:
option
("--port",
port
({
min
: 1024 }))
}); // Firewall rule configuration const
firewallRule
=
object
({
source
:
option
("--source",
ipv4
()),
dest
:
option
("--dest",
ipv4
())
}); // DNS server configuration const
dnsConfig
=
option
("--nameserver",
ipv4
({
allowLoopback
: false, // Loopback doesn't make sense for DNS
allowZero
: false // 0.0.0.0 not valid for nameserver
}));

The parser uses "IPV4" as its default metavar and returns the normalized IPv4 address as a string (e.g., “192.168.1.1”).

hostname() parser

The hostname() parser validates DNS hostnames according to RFC 1123. It supports flexible options for wildcard hostnames, underscores, localhost filtering, and length constraints.

import { 
hostname
} from "@optique/core/valueparser";
// Basic hostname parser const
host
=
hostname
();
// Allow wildcards for certificate validation const
domain
=
hostname
({
allowWildcard
: true });
// Reject localhost const
remoteHost
=
hostname
({
allowLocalhost
: false });
// Service discovery records (with underscores) const
srvRecord
=
hostname
({
allowUnderscore
: true });

Hostname validation rules

The parser validates hostnames according to RFC 1123:

  • Labels are separated by dots (.)
  • Each label must be 1-63 characters long
  • Labels can contain alphanumeric characters and hyphens (-)
  • Labels cannot start or end with a hyphen
  • Total hostname length must not exceed 253 characters (by default)

For example:

  • Valid: "example.com", "sub.example.com", "server-01.local"
  • Invalid: "-example.com" (starts with hyphen), "example..com" (empty label), "a".repeat(64) + ".com" (label too long)

Wildcard hostnames

By default, wildcard hostnames (starting with *.) are rejected. Enable them with the allowWildcard option:

// Allow wildcards for SSL certificate domains
const 
certDomain
=
option
("--domain",
hostname
({
allowWildcard
: true }));
$ example --domain "*.example.com"   # Valid
$ example --domain "*.*.example.com" # Invalid: multiple wildcards not allowed

Underscores in hostnames

While technically invalid per RFC 1123, underscores are commonly used in some contexts like service discovery records. Enable them with allowUnderscore:

// Allow underscores for service discovery
const 
service
=
option
("--srv",
hostname
({
allowUnderscore
: true }));
$ example --srv "_http._tcp.example.com"  # Valid with allowUnderscore
$ example --srv "_http._tcp.example.com"  # Invalid by default

Localhost filtering

The parser accepts "localhost" by default. Reject it with allowLocalhost: false:

// Require remote hosts only
const 
remoteHost
=
option
("--remote",
hostname
({
allowLocalhost
: false }));
$ example --remote "localhost"    # Error: Hostname 'localhost' is not allowed.
$ example --remote "example.com"  # Valid

Length constraints

The default maximum hostname length is 253 characters (the RFC 1123 limit). Customize it with the maxLength option:

// Limit hostname length to 50 characters
const 
shortHost
=
option
("--host",
hostname
({
maxLength
: 50 }));

Custom error messages

Customize error messages for various validation failures:

const 
host
=
option
("--host",
hostname
({
allowWildcard
: false,
allowLocalhost
: false,
errors
: {
invalidHostname
: (
input
) =>
message
`Not a valid hostname: ${
input
}`,
wildcardNotAllowed
:
message
`Wildcards are forbidden`,
localhostNotAllowed
:
message
`Remote hosts only`,
tooLong
: (
hostname
,
max
) =>
message
`Hostname too long (max ${
text
(
max
.
toString
())} chars)`,
}, }));

Common use cases

Web server host configuration:

// Allow localhost for local development
const 
serverHost
=
hostname
({
allowLocalhost
: true });

Remote database connections:

// Require remote hosts only
const 
dbHost
=
hostname
({
allowLocalhost
: false });

SSL certificate domain validation:

// Allow wildcards for certificate domains
const 
certDomain
=
hostname
({
allowWildcard
: true });

The parser uses "HOST" as its default metavar and returns the hostname as-is, preserving the original case.

email() parser

The email() parser validates email addresses according to simplified RFC 5322 addr-spec format. It supports display names, multiple addresses, domain filtering, and quoted local parts for practical email validation use cases.

import { 
email
} from "@optique/core/valueparser";
// Basic email parser const
userEmail
=
email
();
// Multiple comma-separated addresses const
recipients
=
email
({
allowMultiple
: true });
// Allow display names const
from
=
email
({
allowDisplayName
: true });
// Restrict to specific domains const
workEmail
=
email
({
allowedDomains
: ["company.com"] });

Email validation rules

The parser validates email addresses using simplified RFC 5322 rules:

  • Format: local-part@domain
  • Local part: alphanumeric characters, dots (.), hyphens (-), underscores (_), and plus signs (+)
  • Domain part: valid hostname with at least one dot
  • Quoted local parts (e.g., "user name"@example.com) allow spaces and special characters

For example:

  • Valid: "user@example.com", "first.last@example.com", "user+tag@mail.example.com"
  • Invalid: "userexample.com" (no @ sign), "user@example" (no dot in domain), "user..name@example.com" (consecutive dots)

Display name support

By default, display names are rejected. Enable them with the allowDisplayName option:

// Allow "Name <email@example.com>" format
const 
from
=
option
("--from",
email
({
allowDisplayName
: true }));
$ example --from "John Doe <john@example.com>"  # Valid
$ example --from "john@example.com"             # Also valid

The parser extracts the email address and discards the display name. Both formats ("Name <email>" and Name <email>) are accepted.

Multiple email addresses

Parse comma-separated email lists with allowMultiple:

// Accept multiple comma-separated emails
const 
to
=
option
("--to",
email
({
allowMultiple
: true }));
$ example --to "alice@example.com,bob@example.com"  # Valid
$ example --to "alice@example.com, bob@example.com" # Whitespace trimmed

When allowMultiple is true, the parser returns a readonly string[] instead of a single string.

Domain filtering

Restrict accepted email addresses to specific domains with allowedDomains:

// Only accept company email addresses
const 
workEmail
=
option
(
"--email",
email
({
allowedDomains
: ["company.com", "company.org"] })
);
$ example --email "user@company.com"  # Valid
$ example --email "user@gmail.com"    # Error: domain not allowed

Domain matching is case-insensitive.

Lowercase conversion

Convert email addresses to lowercase with the lowercase option:

// Normalize email to lowercase
const 
normalizedEmail
=
option
("--email",
email
({
lowercase
: true }));
$ example --email "User@Example.COM"  # Returns: "user@example.com"

Quoted local parts

The parser supports quoted strings in local parts for special characters:

const 
parser
=
email
();
const
result
=
parser
.
parse
('"user name"@example.com');
// result.value = '"user name"@example.com'

Quoted strings allow spaces and special characters that are normally forbidden in local parts, such as @ signs inside the quotes.

Custom error messages

Customize error messages for validation failures:

const 
workEmail
=
option
("--email",
email
({
allowedDomains
: ["company.com"],
errors
: {
invalidEmail
: (
input
) =>
message
`Invalid email format: ${
input
}`,
domainNotAllowed
: (
email
,
domains
) =>
message
`Email ${
email
} must use company domain`,
}, }));

Common use cases

User registration email:

// Normalize email for consistent storage
const 
userEmail
=
email
({
lowercase
: true });

Notification recipients:

// Multiple recipients with display names
const 
recipients
=
email
({
allowMultiple
: true,
allowDisplayName
: true });

Corporate email restriction:

// Only accept company domain emails
const 
corporateEmail
=
email
({
allowedDomains
: ["company.com", "company.org"],
lowercase
: true,
});

The parser uses "EMAIL" as its default metavar.

socketAddress() parser

The socketAddress() parser validates socket addresses in “host:port” format. It supports both hostnames and IPv4 addresses, configurable separators, default ports, and comprehensive host/port validation options. The parser returns a SocketAddressValue object containing both the host and port components.

import { 
socketAddress
} from "@optique/core/valueparser";
// Basic socket address (requires port) const
endpoint
=
socketAddress
({
requirePort
: true });
// With default port const
server
=
socketAddress
({
defaultPort
: 80 });
// IP addresses only const
bind
=
socketAddress
({
defaultPort
: 8080,
host
: {
type
: "ip" },
}); // Non-privileged ports only const
listen
=
socketAddress
({
defaultPort
: 8080,
port
: {
min
: 1024 },
});

Socket address format

The parser accepts addresses in the following format:

  • With port: host:port (e.g., "localhost:3000", "192.168.1.1:80")
  • Without port: host (only when defaultPort is set, e.g., "example.com")

The separator between host and port can be customized using the separator option.

Port requirements

By default, the port is optional if defaultPort is specified. Control this behavior with the requirePort option:

// Port always required
const 
endpoint
=
option
(
"--endpoint",
socketAddress
({
requirePort
: true })
);
$ example --endpoint "localhost:3000"  # Valid
$ example --endpoint "localhost"       # Error: port required

When requirePort is false (default) and defaultPort is set, the port may be omitted:

// Port optional, defaults to 80
const 
server
=
option
(
"--server",
socketAddress
({
defaultPort
: 80 })
);
$ example --server "example.com:443"  # Uses port 443
$ example --server "example.com"      # Uses default port 80

Host type filtering

The host.type option controls what types of hosts are accepted:

  • "hostname": Accept only valid hostnames
  • "ip": Accept only IP addresses (currently IPv4 only)
  • "both": Accept both hostnames and IP addresses (default)
// Only accept IP addresses for binding
const 
bind
=
option
(
"--bind",
socketAddress
({
defaultPort
: 8080,
host
: {
type
: "ip" },
}) );
$ example --bind "0.0.0.0:8080"     # Valid
$ example --bind "localhost:8080"   # Error: hostname not allowed

Host validation options

Pass options to the underlying hostname or IP parser using host.hostname or host.ip:

// Remote hosts only (no localhost)
const 
remote
=
option
(
"--remote",
socketAddress
({
defaultPort
: 80,
host
: {
type
: "hostname",
hostname
: {
allowLocalhost
: false },
}, }) );
$ example --remote "localhost:80"     # Error: localhost not allowed
$ example --remote "example.com:80"   # Valid

For IP addresses, use host.ip to pass options like allowPrivate:

// Public IPs only
const 
publicServer
=
option
(
"--server",
socketAddress
({
defaultPort
: 443,
host
: {
type
: "ip",
ip
: {
allowPrivate
: false },
}, }) );

Port validation options

Pass port validation options using the port field:

// Non-privileged ports only
const 
listen
=
option
(
"--listen",
socketAddress
({
defaultPort
: 8080,
port
: {
min
: 1024,
max
: 65535 },
}) );
$ example --listen "localhost:80"    # Error: port too low
$ example --listen "localhost:8080"  # Valid

Disallow well-known ports (1-1023):

const 
server
=
option
(
"--server",
socketAddress
({
defaultPort
: 8080,
port
: {
disallowWellKnown
: true },
}) );

Custom separator

Change the separator between host and port:

// Use space as separator
const 
proxy
=
option
(
"--proxy",
socketAddress
({
separator
: " ",
defaultPort
: 8080 })
);
$ example --proxy "localhost 3000"  # Valid
$ example --proxy "localhost:3000"  # Invalid: wrong separator

Return value

The parser returns a SocketAddressValue object:

const 
parser
=
socketAddress
({
defaultPort
: 80 });
const
result
=
parser
.
parse
("example.com:443");
if (
result
.
success
) {
console
.
log
(
result
.
value
.
host
); // "example.com"
console
.
log
(
result
.
value
.
port
); // 443
}

Custom error messages

Customize error messages for validation failures:

const 
endpoint
=
option
(
"--endpoint",
socketAddress
({
requirePort
: true,
errors
: {
invalidFormat
: (
input
) =>
message
`Invalid endpoint: ${
input
}`,
missingPort
:
message
`You must specify a port number`,
}, }) );

Common use cases

Web server configuration:

// Allow any host, default to port 8080
const 
listen
=
socketAddress
({
defaultPort
: 8080 });

Database connections:

// Require explicit host and port
const 
dbServer
=
socketAddress
({
requirePort
: true });

Proxy configuration:

// Accept hostnames and IPs, default port 3128
const 
proxy
=
socketAddress
({
defaultPort
: 3128,
host
: {
type
: "both" },
});

Service binding (IP addresses only):

// Bind to IP addresses only, non-privileged ports
const 
bind
=
socketAddress
({
defaultPort
: 8080,
host
: {
type
: "ip" },
port
: {
min
: 1024 },
});

The parser uses "HOST:PORT" as its default metavar.

portRange() parser

The portRange() parser validates port ranges in the format start-end (e.g., 8000-8080). It supports both number and bigint types, custom separators, and comprehensive validation options including min/max constraints and well-known port filtering.

import { 
portRange
} from "@optique/core/valueparser";
// Basic port range (number type) const
range
=
portRange
();
// Custom separator const
colonRange
=
portRange
({
separator
: ":" });
// Allow single ports (returns {start: 8080, end: 8080}) const
flexibleRange
=
portRange
({
allowSingle
: true });
// Restrict to non-privileged ports const
unprivilegedRange
=
portRange
({
min
: 1024 });
// BigInt type for consistency with other APIs const
bigintRange
=
portRange
({
type
: "bigint" });

Port range format

The parser accepts port ranges in start-end format where both start and end must be valid port numbers (1-65535):

parser
.
parse
("8000-8080"); // { start: 8000, end: 8080 }
parser
.
parse
("80-443"); // { start: 80, end: 443 }
parser
.
parse
("1-65535"); // { start: 1, end: 65535 }

The start port must be less than or equal to the end port:

parser
.
parse
("8080-8000"); // Error: start port must not be greater than end port

Single port mode

When allowSingle is enabled, the parser accepts single port numbers and returns a range where start equals end:

import { 
portRange
} from "@optique/core/valueparser";
const
parser
=
portRange
({
allowSingle
: true });
parser
.
parse
("8080"); // { start: 8080, end: 8080 }
parser
.
parse
("80-443"); // { start: 80, end: 443 }

Without allowSingle, single ports are rejected:

import { 
portRange
} from "@optique/core/valueparser";
const
parser
=
portRange
();
parser
.
parse
("8080"); // Error: must be in the format PORT-PORT

Custom separator

The default separator is "-", but you can specify any string:

import { 
portRange
} from "@optique/core/valueparser";
// Colon separator const
colonRange
=
portRange
({
separator
: ":" });
colonRange
.
parse
("8000:8080"); // { start: 8000, end: 8080 }
// Multi-character separator const
toRange
=
portRange
({
separator
: " to " });
toRange
.
parse
("8000 to 8080"); // { start: 8000, end: 8080 }

The separator is also used in the metavar and error messages:

import { 
portRange
} from "@optique/core/valueparser";
portRange
({
separator
: ":" }).
metavar
; // "PORT:PORT"
portRange
({
separator
: " to " }).
metavar
; // "PORT to PORT"

Min/max constraints

Restrict the allowed port range using min and max options. These constraints apply to both the start and end ports:

import { 
portRange
} from "@optique/core/valueparser";
// Only non-privileged ports const
unprivileged
=
portRange
({
min
: 1024 });
unprivileged
.
parse
("1024-8080"); // { start: 1024, end: 8080 }
unprivileged
.
parse
("80-443"); // Error: must be at least 1024
// Limit to ephemeral port range const
ephemeral
=
portRange
({
min
: 49152,
max
: 65535 });
ephemeral
.
parse
("50000-51000"); // { start: 50000, end: 51000 }
ephemeral
.
parse
("8000-8080"); // Error: must be at least 49152

Well-known port filtering

The disallowWellKnown option rejects well-known ports (1-1023) in both the start and end positions:

import { 
portRange
} from "@optique/core/valueparser";
const
parser
=
portRange
({
disallowWellKnown
: true });
parser
.
parse
("1024-8080"); // { start: 1024, end: 8080 }
parser
.
parse
("80-443"); // Error: must not be a well-known port (1-1023)
parser
.
parse
("1024-1023"); // Error: must not be a well-known port (1-1023)

Number vs bigint types

The parser supports both number (default) and bigint types:

import { 
portRange
} from "@optique/core/valueparser";
// Number type (default) const
numRange
=
portRange
();
numRange
.
parse
("8000-8080"); // { start: 8000, end: 8080 }
// BigInt type const
bigintRange
=
portRange
({
type
: "bigint" });
bigintRange
.
parse
("8000-8080"); // { start: 8000n, end: 8080n }

The bigint type is useful for consistency when working with APIs that use bigint for port numbers.

Return value

The parser returns a PortRangeValue object with start and end properties:

import { 
portRange
} from "@optique/core/valueparser";
interface PortRangeValueNumber { readonly
start
: number;
readonly
end
: number;
} interface PortRangeValueBigInt { readonly
start
: bigint;
readonly
end
: bigint;
}

The type is inferred based on the type option:

import { 
portRange
} from "@optique/core/valueparser";
const
numRange
=
portRange
();
const
result1
=
numRange
.
parse
("8000-8080");
if (
result1
.
success
) {
result1
.
value
.
start
; // type: number
} const
bigintRange
=
portRange
({
type
: "bigint" });
const
result2
=
bigintRange
.
parse
("8000-8080");
if (
result2
.
success
) {
result2
.
value
.
start
; // type: bigint
}

Custom error messages

All error messages can be customized via the errors option:

const 
portOpt
=
option
("--ports",
portRange
({
errors
: {
invalidFormat
:
message
`Port range must be START-END`,
invalidRange
:
message
`START must be ≤ END`,
invalidPort
:
message
`Ports must be 1-65535`,
belowMinimum
: (
port
,
min
) =>
message
`Port ${
text
(
port
.
toString
())} is below minimum ${
text
(
min
.
toString
())}`,
aboveMaximum
: (
port
,
max
) =>
message
`Port ${
text
(
port
.
toString
())} is above maximum ${
text
(
max
.
toString
())}`,
wellKnownNotAllowed
:
message
`System ports (1-1023) are not allowed`,
}, }));

The invalidFormat and invalidRange errors are specific to port ranges, while other errors (invalidPort, belowMinimum, aboveMaximum, wellKnownNotAllowed) are inherited from the underlying port() parser.

Common use cases

Dynamic server port pools:

// Accept ranges or single ports for service binding
const 
ports
=
portRange
({
allowSingle
: true,
min
: 1024,
metavar
: "PORTS",
});

Load balancer configuration:

// Backend server port range
const 
backendPorts
=
portRange
({
min
: 8000,
max
: 9000,
});

Firewall rules:

// Allow specifying port ranges for firewall configuration
const 
allowedPorts
=
portRange
({
allowSingle
: true,
disallowWellKnown
: true,
});

Container port mapping:

// Map container port ranges (colon separator for Docker-style syntax)
const 
portMapping
=
portRange
({
separator
: ":",
min
: 1024,
});

The parser uses "PORT-PORT" as its default metavar (or "PORT{separator}PORT" when a custom separator is specified).

macAddress() parser

The macAddress() parser validates MAC (Media Access Control) addresses, commonly used to identify network interface hardware. It accepts MAC-48 addresses (6 octets, 12 hexadecimal digits) in multiple common formats with support for case conversion and output normalization.

Supported formats

The parser accepts MAC addresses in four standard formats:

import { 
macAddress
} from "@optique/core/valueparser";
const
parser
=
macAddress
();
const
result1
=
parser
.
parse
("00:1A:2B:3C:4D:5E"); // Colon-separated
const
result2
=
parser
.
parse
("00-1A-2B-3C-4D-5E"); // Hyphen-separated
const
result3
=
parser
.
parse
("001A.2B3C.4D5E"); // Cisco format (dot-separated)
const
result4
=
parser
.
parse
("001A2B3C4D5E"); // No separator

By default, the parser accepts any of these formats. You can restrict it to a specific format using the separator option:

// Accept only colon-separated format
const 
colonOnly
=
macAddress
({
separator
: ":" });
const
result
=
colonOnly
.
parse
("00:1A:2B:3C:4D:5E");
if (
result
.
success
) {
result
.
value
; // "00:1A:2B:3C:4D:5E"
} // Rejects other formats const
invalid
=
colonOnly
.
parse
("00-1A-2B-3C-4D-5E");
if (!
invalid
.
success
) {
invalid
.
error
; // "Invalid MAC address."
}

The separator option accepts:

  • ":" - Colon-separated format (e.g., 00:1A:2B:3C:4D:5E)
  • "-" - Hyphen-separated format (e.g., 00-1A-2B-3C-4D-5E)
  • "." - Cisco dot notation (e.g., 001A.2B3C.4D5E)
  • "none" - No separator (e.g., 001A2B3C4D5E)
  • "any" - Accept any format (default)

Case conversion

The parser provides case conversion options for hexadecimal digits:

// Preserve original case (default)
const 
preserveCase
=
macAddress
({
case
: "preserve" });
const
result1
=
preserveCase
.
parse
("00:1a:2B:3c:4D:5e");
if (
result1
.
success
) {
result1
.
value
; // "00:1a:2B:3c:4D:5e"
} // Convert to uppercase const
upperCase
=
macAddress
({
case
: "upper" });
const
result2
=
upperCase
.
parse
("00:1a:2b:3c:4d:5e");
if (
result2
.
success
) {
result2
.
value
; // "00:1A:2B:3C:4D:5E"
} // Convert to lowercase const
lowerCase
=
macAddress
({
case
: "lower" });
const
result3
=
lowerCase
.
parse
("00:1A:2B:3C:4D:5E");
if (
result3
.
success
) {
result3
.
value
; // "00:1a:2b:3c:4d:5e"
}

Output normalization

The outputSeparator option normalizes the output format regardless of the input format:

// Normalize all inputs to colon-separated
const 
normalize
=
macAddress
({
outputSeparator
: ":",
case
: "upper",
}); const
result1
=
normalize
.
parse
("00-1a-2b-3c-4d-5e");
if (
result1
.
success
) {
result1
.
value
; // "00:1A:2B:3C:4D:5E"
} const
result2
=
normalize
.
parse
("001a.2b3c.4d5e");
if (
result2
.
success
) {
result2
.
value
; // "00:1A:2B:3C:4D:5E"
}

The outputSeparator option accepts the same values as separator except "any". When not specified, the output preserves the input format.

Return value

The parser returns a formatted string according to the case and outputSeparator options:

const 
parser
=
macAddress
({
outputSeparator
: ":",
case
: "upper",
}); const
result
=
parser
.
parse
("00-1a-2b-3c-4d-5e");
if (
result
.
success
) {
const
mac
: string =
result
.
value
; // "00:1A:2B:3C:4D:5E"
}

Custom error messages

You can customize error messages using the errors option:

const 
parser
=
macAddress
({
errors
: {
invalidMacAddress
: (
input
) =>
message
`Invalid MAC address: ${
text
(
input
)}. Expected format: XX:XX:XX:XX:XX:XX`,
}, }); const
result
=
parser
.
parse
("not-a-mac");
if (!
result
.
success
) {
result
.
error
; // "Invalid MAC address: not-a-mac. Expected format: XX:XX:XX:XX:XX:XX"
}

Common use cases

Network device configuration:

// Standardize MAC addresses to uppercase colon format
const 
deviceMac
=
macAddress
({
outputSeparator
: ":",
case
: "upper",
});

Cisco router configuration:

// Accept only Cisco dot notation format
const 
ciscoMac
=
macAddress
({
separator
: ".",
case
: "lower",
});

Access control lists:

// Accept any format but normalize for storage
const 
aclMac
=
macAddress
({
outputSeparator
: "none",
case
: "upper",
});

Network monitoring tools:

// Accept any format for user convenience
const 
monitorMac
=
macAddress
();

The parser uses "MAC" as its default metavar.

domain() parser

The domain() parser validates domain names according to RFC 1035 with configurable options for subdomain filtering, TLD restrictions, minimum label requirements, and case normalization.

Basic validation

The parser validates domain names with the following rules:

  • Each label (part separated by dots) must be 1-63 characters
  • Labels can contain alphanumeric characters and hyphens
  • Labels cannot start or end with a hyphen
  • By default, requires at least 2 labels (e.g., example.com)
import { 
domain
} from "@optique/core/valueparser";
const
parser
=
domain
();
const
result1
=
parser
.
parse
("example.com");
if (
result1
.
success
) {
result1
.
value
; // "example.com"
} const
result2
=
parser
.
parse
("www.example.com");
if (
result2
.
success
) {
result2
.
value
; // "www.example.com"
} const
result3
=
parser
.
parse
("api.staging.example.com");
if (
result3
.
success
) {
result3
.
value
; // "api.staging.example.com"
}

Subdomain filtering

Use the allowSubdomains option to restrict to root domains only:

// Accept only root domains (2 labels)
const 
rootOnly
=
domain
({
allowSubdomains
: false });
const
result1
=
rootOnly
.
parse
("example.com");
if (
result1
.
success
) {
result1
.
value
; // "example.com"
} // Rejects subdomains const
result2
=
rootOnly
.
parse
("www.example.com");
if (!
result2
.
success
) {
result2
.
error
; // "Subdomains are not allowed, but got www.example.com."
}

TLD restrictions

Use the allowedTLDs option to restrict accepted top-level domains:

// Accept only specific TLDs
const 
restrictedTLD
=
domain
({
allowedTLDs
: ["com", "org", "net"] });
const
result1
=
restrictedTLD
.
parse
("example.com");
if (
result1
.
success
) {
result1
.
value
; // "example.com"
} // Case-insensitive TLD matching const
result2
=
restrictedTLD
.
parse
("example.COM");
if (
result2
.
success
) {
result2
.
value
; // "example.COM"
} // Rejects disallowed TLDs const
result3
=
restrictedTLD
.
parse
("example.io");
if (!
result3
.
success
) {
result3
.
error
; // "Top-level domain io is not allowed. Allowed TLDs: com, org, net."
}

Minimum labels

Use the minLabels option to require a specific number of labels:

// Require at least 3 labels
const 
threeLabels
=
domain
({
minLabels
: 3 });
const
result1
=
threeLabels
.
parse
("www.example.com");
if (
result1
.
success
) {
result1
.
value
; // "www.example.com"
} const
result2
=
threeLabels
.
parse
("example.com");
if (!
result2
.
success
) {
result2
.
error
; // "Domain example.com must have at least 3 labels."
} // Allow single-label domains (e.g., "localhost") const
singleLabel
=
domain
({
minLabels
: 1 });
const
result3
=
singleLabel
.
parse
("localhost");
if (
result3
.
success
) {
result3
.
value
; // "localhost"
}

Case normalization

Use the lowercase option to normalize domain names to lowercase:

// Preserve original case (default)
const 
preserveCase
=
domain
();
const
result1
=
preserveCase
.
parse
("Example.COM");
if (
result1
.
success
) {
result1
.
value
; // "Example.COM"
} // Convert to lowercase const
lowerCase
=
domain
({
lowercase
: true });
const
result2
=
lowerCase
.
parse
("Example.COM");
if (
result2
.
success
) {
result2
.
value
; // "example.com"
}

Return value

The parser returns a string representing the domain name, optionally normalized to lowercase:

const 
parser
=
domain
({
lowercase
: true });
const
result
=
parser
.
parse
("WWW.Example.COM");
if (
result
.
success
) {
const
domainName
: string =
result
.
value
; // "www.example.com"
}

Custom error messages

You can customize error messages using the errors option:

const 
parser
=
domain
({
allowSubdomains
: false,
allowedTLDs
: ["com", "org"],
errors
: {
invalidDomain
: (
input
) =>
message
`Invalid domain: ${
text
(
input
)}`,
subdomainsNotAllowed
: (
domain
) =>
message
`Only root domains allowed, got: ${
text
(
domain
)}`,
tldNotAllowed
: (
tld
,
allowed
) =>
message
`TLD ${
text
(
tld
)} not in: ${
text
(
allowed
.
join
(", "))}`,
tooFewLabels
: (
domain
,
min
) =>
message
`${
text
(
domain
)} needs ${
text
(
min
.
toString
())} labels`,
}, }); const
result
=
parser
.
parse
("example..com");
if (!
result
.
success
) {
result
.
error
; // Custom error message
}

Common use cases

Website configuration:

// Accept any valid domain, normalize to lowercase
const 
websiteDomain
=
domain
({
lowercase
: true });

Email domain validation:

// Restrict to specific TLDs for corporate email
const 
emailDomain
=
domain
({
allowedTLDs
: ["com", "org", "edu"],
lowercase
: true,
});

DNS configuration:

// Root domains only for DNS zone setup
const 
zoneDomain
=
domain
({
allowSubdomains
: false,
lowercase
: true,
});

API endpoint configuration:

// Any valid domain with minimum 2 labels
const 
apiDomain
=
domain
({
minLabels
: 2,
lowercase
: true,
});

The parser uses "DOMAIN" as its default metavar.

ipv6() parser

The ipv6() parser validates and normalizes IPv6 addresses to canonical form (lowercase, compressed using :: notation where appropriate). It supports full addresses, compressed notation, and IPv4-mapped IPv6 addresses, with configurable address type restrictions.

Basic validation

The parser validates IPv6 addresses in various formats:

import { 
ipv6
} from "@optique/core/valueparser";
const
parser
=
ipv6
();
// Full format const
result1
=
parser
.
parse
("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
if (
result1
.
success
) {
result1
.
value
; // "2001:db8:85a3::8a2e:370:7334" (compressed)
} // Compressed format const
result2
=
parser
.
parse
("2001:db8::1");
if (
result2
.
success
) {
result2
.
value
; // "2001:db8::1"
} // IPv4-mapped IPv6 const
result3
=
parser
.
parse
("::ffff:192.0.2.1");
if (
result3
.
success
) {
result3
.
value
; // "::ffff:c000:201"
} // Loopback const
result4
=
parser
.
parse
("::1");
if (
result4
.
success
) {
result4
.
value
; // "::1"
}

Address type filtering

The parser provides options to restrict specific address types:

// No loopback addresses
const 
noLoopback
=
ipv6
({
allowLoopback
: false });
const
result1
=
noLoopback
.
parse
("::1");
if (!
result1
.
success
) {
result1
.
error
; // "::1 is a loopback address."
} // Global unicast only (no link-local, no unique local) const
publicOnly
=
ipv6
({
allowLinkLocal
: false,
allowUniqueLocal
: false,
}); const
result2
=
publicOnly
.
parse
("fe80::1");
if (!
result2
.
success
) {
result2
.
error
; // "fe80::1 is a link-local address."
} const
result3
=
publicOnly
.
parse
("fc00::1");
if (!
result3
.
success
) {
result3
.
error
; // "fc00::1 is a unique local address."
}

Address type categories

The parser recognizes these IPv6 address types:

  • Loopback (::1): Local loopback address
  • Link-local (fe80::/10): Addresses for single network segment
  • Unique local (fc00::/7): Private addresses for local communication
  • Multicast (ff00::/8): Addresses for multicast groups
  • Zero (::): All-zeros address

Normalization

The parser automatically normalizes IPv6 addresses to canonical form:

  • Converts to lowercase
  • Removes leading zeros from each group
  • Compresses the longest sequence of consecutive zero groups with ::
const 
parser
=
ipv6
();
// Uppercase converted to lowercase const
result1
=
parser
.
parse
("2001:DB8:85A3::8A2E:370:7334");
if (
result1
.
success
) {
result1
.
value
; // "2001:db8:85a3::8a2e:370:7334"
} // Leading zeros removed and compressed const
result2
=
parser
.
parse
("2001:0db8:0000:0000:0000:0000:0000:0001");
if (
result2
.
success
) {
result2
.
value
; // "2001:db8::1"
}

Return value

The parser returns a string representing the normalized IPv6 address in canonical form (lowercase, compressed).

The parser uses "IPV6" as its default metavar.

ip() parser

The ip() parser accepts both IPv4 and IPv6 addresses, delegating to the ipv4() and ipv6() parsers based on the detected format. It can be configured to accept only IPv4, only IPv6, or both (default).

Basic validation

By default, the parser accepts both IP versions:

import { 
ip
} from "@optique/core/valueparser";
const
parser
=
ip
();
// IPv4 address const
result1
=
parser
.
parse
("192.0.2.1");
if (
result1
.
success
) {
result1
.
value
; // "192.0.2.1"
} // IPv6 address const
result2
=
parser
.
parse
("2001:db8::1");
if (
result2
.
success
) {
result2
.
value
; // "2001:db8::1"
}

Version filtering

Use the version option to restrict to a specific IP version:

// IPv4 only
const 
ipv4Only
=
ip
({
version
: 4 });
const
result1
=
ipv4Only
.
parse
("192.0.2.1");
if (
result1
.
success
) {
result1
.
value
; // "192.0.2.1"
} const
result2
=
ipv4Only
.
parse
("2001:db8::1");
if (!
result2
.
success
) {
result2
.
error
; // "Expected a valid IP address, but got 2001:db8::1."
} // IPv6 only const
ipv6Only
=
ip
({
version
: 6 });
const
result3
=
ipv6Only
.
parse
("2001:db8::1");
if (
result3
.
success
) {
result3
.
value
; // "2001:db8::1"
}

Passing options to IPv4/IPv6 parsers

The parser accepts separate options for IPv4 and IPv6 validation:

// Public IPs only (both versions)
const 
publicOnly
=
ip
({
ipv4
: {
allowPrivate
: false,
allowLoopback
: false },
ipv6
: {
allowLinkLocal
: false,
allowUniqueLocal
: false },
}); const
result1
=
publicOnly
.
parse
("192.168.1.1");
if (!
result1
.
success
) {
result1
.
error
; // "192.168.1.1 is a private IP address."
} const
result2
=
publicOnly
.
parse
("fe80::1");
if (!
result2
.
success
) {
result2
.
error
; // "fe80::1 is a link-local address."
}

Shared error messages

The parser supports shared error messages that apply to both IP versions:

const 
parser
=
ip
({
errors
: {
loopbackNotAllowed
: [
{
type
: "text",
text
: "Loopback addresses are not allowed" },
] satisfies
Message
,
},
ipv4
: {
allowLoopback
: false },
ipv6
: {
allowLoopback
: false },
}); const
result1
=
parser
.
parse
("127.0.0.1");
if (!
result1
.
success
) {
result1
.
error
; // "Loopback addresses are not allowed"
} const
result2
=
parser
.
parse
("::1");
if (!
result2
.
success
) {
result2
.
error
; // "Loopback addresses are not allowed"
}

Return value

The parser returns a normalized IP address string. IPv4 addresses are returned as-is, while IPv6 addresses are normalized to canonical form (lowercase, compressed).

When version is "both", the parser tries IPv4 first, then IPv6 if IPv4 fails. This means IPv4-mapped IPv6 addresses like ::ffff:192.0.2.1 are parsed as IPv6.

The parser uses "IP" as its default metavar.

cidr() parser

The cidr() parser validates CIDR notation (IP address with prefix length) and returns a structured object containing the normalized IP address, prefix length, and IP version.

Basic validation

The parser validates CIDR notation for both IPv4 and IPv6:

import { 
cidr
} from "@optique/core/valueparser";
const
parser
=
cidr
();
// IPv4 CIDR const
result1
=
parser
.
parse
("192.0.2.0/24");
if (
result1
.
success
) {
result1
.
value
.
address
; // "192.0.2.0"
result1
.
value
.
prefix
; // 24
result1
.
value
.
version
; // 4
} // IPv6 CIDR const
result2
=
parser
.
parse
("2001:db8::/32");
if (
result2
.
success
) {
result2
.
value
.
address
; // "2001:db8::"
result2
.
value
.
prefix
; // 32
result2
.
value
.
version
; // 6
}

Prefix validation

The parser validates prefix lengths based on IP version:

  • IPv4: 0-32
  • IPv6: 0-128
const 
parser
=
cidr
();
// Valid IPv4 prefix const
result1
=
parser
.
parse
("192.0.2.0/32");
if (
result1
.
success
) {
result1
.
value
.
prefix
; // 32
} // Invalid IPv4 prefix (>32) const
result2
=
parser
.
parse
("192.0.2.0/33");
if (!
result2
.
success
) {
result2
.
error
; // "Expected a prefix length between 0 and 32 for IPv4, but got 33."
} // Valid IPv6 prefix const
result3
=
parser
.
parse
("2001:db8::/128");
if (
result3
.
success
) {
result3
.
value
.
prefix
; // 128
}

Prefix constraints

Use minPrefix and maxPrefix options to constrain the prefix length:

// Subnet sizes between /16 and /24
const 
subnet
=
cidr
({
version
: 4,
minPrefix
: 16,
maxPrefix
: 24,
}); const
result1
=
subnet
.
parse
("192.0.2.0/20");
if (
result1
.
success
) {
result1
.
value
.
prefix
; // 20
} const
result2
=
subnet
.
parse
("192.0.2.0/8");
if (!
result2
.
success
) {
result2
.
error
; // "Expected a prefix length greater than or equal to 16, but got 8."
} const
result3
=
subnet
.
parse
("192.0.2.0/28");
if (!
result3
.
success
) {
result3
.
error
; // "Expected a prefix length less than or equal to 24, but got 28."
}

Version filtering

Use the version option to restrict to IPv4 or IPv6 CIDR:

// IPv4 CIDR only
const 
ipv4Cidr
=
cidr
({
version
: 4 });
const
result1
=
ipv4Cidr
.
parse
("192.0.2.0/24");
if (
result1
.
success
) {
result1
.
value
.
version
; // 4
} const
result2
=
ipv4Cidr
.
parse
("2001:db8::/32");
if (!
result2
.
success
) {
result2
.
error
; // "Expected a valid CIDR notation, but got 2001:db8::/32."
}

IP address validation

The parser delegates IP address validation to ipv4() and ipv6() parsers, so you can pass IPv4 and IPv6 options:

// Public IP ranges only
const 
publicCidr
=
cidr
({
ipv4
: {
allowPrivate
: false },
ipv6
: {
allowLinkLocal
: false,
allowUniqueLocal
: false },
}); const
result1
=
publicCidr
.
parse
("192.168.0.0/24");
if (!
result1
.
success
) {
result1
.
error
; // "192.168.0.0 is a private IP address."
}

Return value

The parser returns a CidrValue object with three fields:

  • address: The normalized IP address (string)
  • prefix: The prefix length (number)
  • version: The IP version (4 or 6)
const 
parser
=
cidr
();
const
result
=
parser
.
parse
("192.0.2.0/24");
if (
result
.
success
) {
const {
address
,
prefix
,
version
} =
result
.
value
;
console
.
log
(`${
address
}/${
prefix
} (IPv${
version
})`);
// "192.0.2.0/24 (IPv4)" }

IPv6 addresses are normalized to canonical form (lowercase, compressed):

const 
parser
=
cidr
();
const
result
=
parser
.
parse
("2001:0DB8:0000:0000:0000:0000:0000:0000/32");
if (
result
.
success
) {
result
.
value
.
address
; // "2001:db8::"
result
.
value
.
prefix
; // 32
}

The parser uses "CIDR" as its default metavar.

path() parser

The path() parser validates file system paths with comprehensive options for existence checking, type validation, and file extension filtering. Unlike other built-in value parsers, path() is provided by the @optique/run package since it uses Node.js file system APIs.

import { 
path
} from "@optique/run/valueparser";
// Basic path parser (any path, no validation) const
configPath
=
path
({
metavar
: "CONFIG" });
// File must exist const
inputFile
=
path
({
metavar
: "FILE",
mustExist
: true,
type
: "file" });
// Directory must exist const
outputDir
=
path
({
metavar
: "DIR",
mustExist
: true,
type
: "directory" });
// Config files with specific extensions const
configFile
=
path
({
metavar
: "CONFIG",
mustExist
: true,
type
: "file",
extensions
: [".json", ".yaml", ".yml"]
});

Path validation options

The path() parser accepts comprehensive configuration options:

interface PathOptions {
  
metavar
?: string; // Custom metavar (default: "PATH")
mustExist
?: boolean; // Path must exist on filesystem (default: false)
mustNotExist
?: boolean; // Path must not exist (default: false)
type
?: "file" | "directory" | "either"; // Expected path type (default: "either")
allowCreate
?: boolean; // Allow creating new files (default: false)
extensions
?: string[]; // Allowed file extensions (e.g., [".json", ".txt"])
}

NOTE

The mustExist and mustNotExist options are mutually exclusive. You cannot set both to true at the same time—TypeScript will catch this as a compile-time error.

Existence validation

When mustExist: true, the parser verifies that the path exists on the file system:

import { 
path
} from "@optique/run/valueparser";
// Must exist (file or directory) const
existingPath
=
path
({
mustExist
: true });
// Must be an existing file const
existingFile
=
path
({
mustExist
: true,
type
: "file",
metavar
: "INPUT_FILE"
}); // Must be an existing directory const
existingDir
=
path
({
mustExist
: true,
type
: "directory",
metavar
: "OUTPUT_DIR"
});

Non-existence validation

When mustNotExist: true, the parser rejects paths that already exist on the file system. This is useful for output files where you want to prevent accidental overwrites:

import { 
path
} from "@optique/run/valueparser";
// Output file must not exist (prevent accidental overwrites) const
outputFile
=
path
({
mustNotExist
: true,
type
: "file",
metavar
: "OUTPUT_FILE"
}); // Output file with extension validation const
reportFile
=
path
({
mustNotExist
: true,
extensions
: [".json", ".csv"],
metavar
: "REPORT"
}); // Combine with allowCreate to also check parent directory const
logFile
=
path
({
mustNotExist
: true,
allowCreate
: true,
metavar
: "LOG_FILE"
});

File creation validation

With allowCreate: true, the parser validates that the parent directory exists for new files:

import { 
path
} from "@optique/run/valueparser";
// Allow creating new files (parent directory must exist) const
newFile
=
path
({
allowCreate
: true,
type
: "file",
metavar
: "LOG_FILE"
}); // Combination: file can be new or existing const
flexibleFile
=
path
({
type
: "file",
allowCreate
: true,
extensions
: [".log", ".txt"]
});

Extension filtering

Restrict file paths to specific extensions using the extensions option:

import { 
path
} from "@optique/run/valueparser";
// Configuration files only const
configFile
=
path
({
mustExist
: true,
type
: "file",
extensions
: [".json", ".yaml", ".yml", ".toml"],
metavar
: "CONFIG_FILE"
}); // Image files only const
imageFile
=
path
({
mustExist
: true,
type
: "file",
extensions
: [".jpg", ".jpeg", ".png", ".gif", ".webp"],
metavar
: "IMAGE_FILE"
}); // Script files (existing or new) const
scriptFile
=
path
({
allowCreate
: true,
type
: "file",
extensions
: [".js", ".ts", ".py", ".sh"],
metavar
: "SCRIPT"
});

Error messages

The path() parser provides specific error messages for different validation failures:

$ myapp --config "nonexistent.json"
Error: Path nonexistent.json does not exist.

$ myapp --input "directory_not_file"
Error: Expected a file, but directory_not_file is not a file.

$ myapp --config "config.txt"
Error: Expected file with extension .json, .yaml, .yml, got .txt.

$ myapp --output "new_file/in/nonexistent/dir.txt"
Error: Parent directory new_file/in/nonexistent does not exist.

$ myapp --output "existing_file.txt"
Error: Path existing_file.txt already exists.

Git integration

See the Git integration page for documentation on using Git reference parsers with Optique.

Temporal integration

See the Temporal integration page for documentation on using Temporal API parsers with Optique.

Zod integration

See the Zod integration page for documentation on using Zod schemas with Optique.

Valibot integration

See the Valibot integration page for documentation on using Valibot schemas with Optique.

Creating custom value parsers

When the built-in value parsers don't meet your needs, you can create custom value parsers by implementing the ValueParser<T> interface. Custom parsers integrate seamlessly with Optique's type system and provide the same error handling and help text generation as built-in parsers.

ValueParser interface

The ValueParser<M, T> interface defines three required properties plus a mode marker:

interface 
ValueParser
<
M
extends
Mode
,
T
> {
readonly
$mode
:
M
;
readonly
metavar
:
NonEmptyString
;
parse
(
input
: string):
ModeValue
<
M
,
ValueParserResult
<
T
>>;
format
(
value
:
T
): string;
}
metavar
The placeholder text shown in help messages (usually uppercase). Must be a non-empty string—TypeScript will reject empty string literals at compile time, and factory functions will throw TypeError at runtime if given an empty string.
parse()
Converts string input to typed value or returns error
format()
Converts typed value back to string for display

Basic custom parser

Here's a simple custom parser for IPv4 addresses:

import { 
message
} from "@optique/core/message";
import type {
ValueParser
,
ValueParserResult
} from "@optique/core/valueparser";
interface IPv4Address {
octets
: [number, number, number, number];
toString
(): string;
} function
ipv4
():
ValueParser
<"sync", IPv4Address> {
return {
$mode
: "sync",
metavar
: "IP_ADDRESS",
parse
(
input
: string):
ValueParserResult
<IPv4Address> {
const
parts
=
input
.
split
('.');
if (
parts
.
length
!== 4) {
return {
success
: false,
error
:
message
`Expected IPv4 address in format a.b.c.d, but got ${
input
}.`
}; } const
octets
: number[] = [];
for (const
part
of
parts
) {
const
num
=
parseInt
(
part
, 10);
if (
isNaN
(
num
) ||
num
< 0 ||
num
> 255) {
return {
success
: false,
error
:
message
`Invalid IPv4 octet: ${
part
}. Must be 0-255.`
}; }
octets
.
push
(
num
);
} return {
success
: true,
value
: {
octets
:
octets
as [number, number, number, number],
toString
() {
return
octets
.
join
('.');
} } }; },
format
(
value
: IPv4Address): string {
return
value
.
toString
();
} }; }

Parser with options

More sophisticated parsers can accept configuration options:

import { 
message
} from "@optique/core/message";
import type {
NonEmptyString
,
ValueParser
,
ValueParserResult
,
} from "@optique/core/valueparser"; interface DateParserOptions {
metavar
?:
NonEmptyString
;
format
?: 'iso' | 'us' | 'eu';
allowFuture
?: boolean;
} function
date
(
options
: DateParserOptions = {}):
ValueParser
<"sync", Date> {
const {
metavar
= "DATE",
format
= 'iso',
allowFuture
= true } =
options
;
return {
$mode
: "sync",
metavar
,
parse
(
input
: string):
ValueParserResult
<Date> {
let
date
: Date;
// Parse according to format switch (
format
) {
case 'iso':
date
= new
Date
(
input
);
break; case 'us': // MM/DD/YYYY format const
usMatch
=
input
.
match
(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
if (!
usMatch
) {
return {
success
: false,
error
:
message
`Expected US date format MM/DD/YYYY, but got ${
input
}.`
}; }
date
= new
Date
(
parseInt
(
usMatch
[3]),
parseInt
(
usMatch
[1]) - 1,
parseInt
(
usMatch
[2]));
break; case 'eu': // DD/MM/YYYY format const
euMatch
=
input
.
match
(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
if (!
euMatch
) {
return {
success
: false,
error
:
message
`Expected EU date format DD/MM/YYYY, but got ${
input
}.`
}; }
date
= new
Date
(
parseInt
(
euMatch
[3]),
parseInt
(
euMatch
[2]) - 1,
parseInt
(
euMatch
[1]));
break; } // Validate parsed date if (
isNaN
(
date
.
getTime
())) {
return {
success
: false,
error
:
message
`Invalid date: ${
input
}.`
}; } // Check future constraint if (!
allowFuture
&&
date
> new
Date
()) {
return {
success
: false,
error
:
message
`Future dates not allowed, but got ${
input
}.`
}; } return {
success
: true,
value
:
date
};
},
format
(
value
: Date): string {
switch (
format
) {
case 'iso': return
value
.
toISOString
().
split
('T')[0];
case 'us': return `${
value
.
getMonth
() + 1}/${
value
.
getDate
()}/${
value
.
getFullYear
()}`;
case 'eu': return `${
value
.
getDate
()}/${
value
.
getMonth
() + 1}/${
value
.
getFullYear
()}`;
} } }; } // Usage const
birthDate
=
date
({
format
: 'us',
allowFuture
: false,
metavar
: "BIRTH_DATE"
});

Integration with parsers

Custom value parsers work seamlessly with Optique's parser combinators:

import { 
object
} from "@optique/core/constructs";
import {
parse
} from "@optique/core/parser";
import {
argument
,
option
} from "@optique/core/primitives";
import {
integer
} from "@optique/core/valueparser";
const
serverConfig
=
object
({
address
:
option
("--bind",
ipv4
()),
startDate
:
argument
(
date
({
format
: 'iso' })),
port
:
option
("-p", "--port",
integer
({
min
: 1,
max
: 0xffff }))
}); // Full type safety with custom types const
config
=
parse
(
serverConfig
, [
"--bind", "192.168.1.100", "--port", "8080", "2023-12-25" ]); if (
config
.
success
) {
console
.
log
(`Binding to ${
config
.
value
.
address
.
toString
()}:${
config
.
value
.
port
}.`);
console
.
log
(`Start date: ${
config
.
value
.
startDate
.
toDateString
()}.`);
}

Validation best practices

When creating custom value parsers, follow these best practices:

Clear error messages

Provide specific, actionable error messages that help users understand what went wrong:

// Good: Specific and helpful
return {
  
success
: false,
error
:
message
`Expected IPv4 address in format a.b.c.d, but got ${
input
}.`
}; // Bad: Vague and unhelpful return {
success
: false,
error
:
message
`Invalid input.`
};

Comprehensive validation

Validate all aspects of your input format:

parse
(
input
: string):
ValueParserResult
<
T
> {
// 1. Format validation if (!
correctFormat
(
input
)) {
return {
success
: false,
error
:
formatError
};
} // 2. Range/constraint validation if (!
withinBounds
(
input
)) {
return {
success
: false,
error
:
boundsError
};
} // 3. Semantic validation if (!
semanticallyValid
(
input
)) {
return {
success
: false,
error
:
semanticError
};
} return {
success
: true,
value
:
parseValue
(
input
) };
}

Consistent metavar naming

Use descriptive, uppercase metavar names that clearly indicate the expected input:

// Good metavar examples
metavar: "EMAIL"
metavar: "FILE"
metavar: "PORT"
metavar: "UUID"

// Poor metavar examples
metavar: "input"
metavar: "value"
metavar: "thing"

Custom value parsers extend Optique's type safety and validation capabilities to handle domain-specific data types while maintaining consistency with the built-in parsers and providing excellent integration with TypeScript's type system.

Async value parsers

This API is available since Optique 0.9.0.

Value parsers can operate in either synchronous ("sync") or asynchronous ("async") mode. The mode is declared via the $mode property, which affects the return types of the parse() and suggest() methods.

All built-in value parsers are synchronous, but you can create async parsers for scenarios like:

  • Validating values against a remote API
  • Reading configuration from external sources
  • Performing I/O-based validation (e.g., checking DNS records)

Creating async value parsers

An async value parser declares $mode: "async" and returns a Promise from its parse() method:

import { type 
ValueParser
, type
ValueParserResult
} from "@optique/core/valueparser";
import {
message
} from "@optique/core/message";
// An async value parser that validates a URL by checking if it's reachable function
reachableUrl
():
ValueParser
<"async", URL> {
return {
$mode
: "async",
metavar
: "URL",
async
parse
(
input
: string):
Promise
<
ValueParserResult
<URL>> {
// First validate URL format let
url
: URL;
try {
url
= new
URL
(
input
);
} catch { return {
success
: false,
error
:
message
`Invalid URL format: ${
input
}.`,
}; } // Then check if the URL is reachable try { const
response
= await
fetch
(
url
, {
method
: "HEAD" });
if (!
response
.
ok
) {
return {
success
: false,
error
:
message
`URL ${
input
} returned status ${
response
.
status
.
toString
()}.`,
}; } } catch (
e
) {
return {
success
: false,
error
:
message
`Could not reach URL ${
input
}.`,
}; } return {
success
: true,
value
:
url
};
},
format
(
value
: URL): string {
return
value
.
toString
();
}, }; }

Mode propagation

When you use an async value parser with primitives and combinators, the mode automatically propagates through the parser tree. If any value parser in a composite parser is async, the entire parser becomes async:

// This parser is async because reachableUrl() is async
const 
parser
=
object
({
endpoint
:
option
("--endpoint",
reachableUrl
()), // async
name
:
option
("-n", "--name",
string
()), // sync
}); // parser.$mode is "async"

The mode is tracked at compile time through TypeScript's type system, ensuring type safety when working with async parsers.

Parsing with async parsers

For async parsers, use parseAsync() instead of parse():

// parseAsync() returns a Promise
const 
result
= await
parseAsync
(
parser
, ["--endpoint", "https://api.example.com"]);
if (
result
.
success
) {
console
.
log
(`Connecting to ${
result
.
value
.
endpoint
.
toString
()}.`);
}

The parseAsync() function works with both sync and async parsers, always returning a Promise. For sync-only parsers, use parseSync() which returns the result directly.

Async suggestions

Async value parsers can also provide async completion suggestions by returning an AsyncIterable from the suggest() method:

import type { 
Suggestion
,
ValueParser
,
ValueParserResult
} from "@optique/core";
import {
message
} from "@optique/core/message";
// A value parser that suggests valid user IDs from a remote service function
userId
():
ValueParser
<"async", string> {
return {
$mode
: "async",
metavar
: "USER_ID",
async
parse
(
input
: string):
Promise
<
ValueParserResult
<string>> {
// Validate against remote service... return {
success
: true,
value
:
input
};
},
format
(
value
: string): string {
return
value
;
}, async *
suggest
(
prefix
: string):
AsyncIterable
<
Suggestion
> {
// Fetch matching user IDs from remote service const
response
= await
fetch
(
`https://api.example.com/users?prefix=${
encodeURIComponent
(
prefix
)}`
); const
users
= await
response
.
json
() as {
id
: string;
name
: string }[];
for (const
user
of
users
) {
yield {
kind
: "literal",
text
:
user
.
id
,
description
:
message
`${
user
.
name
}`,
}; } }, }; }

Similarly, use suggestAsync() to get suggestions from async parsers:

// suggestAsync() returns a Promise with an array of suggestions
const 
suggestions
= await
suggestAsync
(
parser
, ["us"]);
for (const
suggestion
of
suggestions
) {
if (
suggestion
.
kind
=== "literal") {
console
.
log
(
suggestion
.
text
);
} }

Completion suggestions

This API is available since Optique 0.6.0.

Value parsers can implement an optional suggest() method to provide intelligent completion suggestions for shell completion. This method enables users to discover valid values by pressing Tab, improving usability and reducing input errors.

Built-in parser suggestions

Many built-in value parsers automatically provide completion suggestions:

import { 
choice
,
locale
,
url
} from "@optique/core/valueparser";
import {
timeZone
} from "@optique/temporal";
// Choice parser suggests all available options const
format
=
choice
(["json", "yaml", "xml"]);
// Completing "j" suggests: ["json"] // URL parser suggests protocol completions when allowedProtocols is set const
apiUrl
=
url
({
allowedProtocols
: ["https:", "http:"] });
// Completing "ht" suggests: ["http://", "https://"] // Locale parser suggests common locale identifiers const
userLocale
=
locale
();
// Completing "en" suggests: ["en", "en-US", "en-GB", "en-CA", ...] // Timezone parser uses Intl.supportedValuesOf for dynamic suggestions const
timezone
=
timeZone
();
// Completing "America/" suggests: ["America/New_York", "America/Chicago", ...]

Custom suggestion implementation

Implement the suggest() method in custom value parsers to provide domain-specific completions:

import { type 
ValueParser
, type
ValueParserResult
} from "@optique/core/valueparser";
import { type
Suggestion
} from "@optique/core/parser";
import {
message
} from "@optique/core/message";
function
httpMethod
():
ValueParser
<"sync", string> {
const
methods
= ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
return {
$mode
: "sync",
metavar
: "METHOD",
parse
(
input
: string):
ValueParserResult
<string> {
const
method
=
input
.
toUpperCase
();
if (
methods
.
includes
(
method
)) {
return {
success
: true,
value
:
method
};
} return {
success
: false,
// Note: For proper formatting of choice lists, see the "Formatting choice lists" // section in the Concepts guide on Messages
error
:
message
`Invalid HTTP method: ${
input
}. Valid methods: ${
methods
.
join
(", ")}.`,
}; },
format
(
value
: string): string {
return
value
;
}, *
suggest
(
prefix
: string):
Iterable
<
Suggestion
> {
for (const
method
of
methods
) {
if (
method
.
toLowerCase
().
startsWith
(
prefix
.
toLowerCase
())) {
yield {
kind
: "literal",
text
:
method
,
description
:
message
`HTTP ${
method
} request method`
}; } } }, }; }

File completion delegation

For file and directory inputs, delegate completion to the shell's native file system integration using file-type suggestions:

import { type 
ValueParser
, type
ValueParserResult
} from "@optique/core/valueparser";
import { type
Suggestion
} from "@optique/core/parser";
import {
message
} from "@optique/core/message";
function
configFile
():
ValueParser
<"sync", string> {
return {
$mode
: "sync",
metavar
: "CONFIG",
parse
(
input
: string):
ValueParserResult
<string> {
// Validation logic here return {
success
: true,
value
:
input
};
},
format
(
value
: string): string {
return
value
;
}, *
suggest
(
prefix
: string):
Iterable
<
Suggestion
> {
yield {
kind
: "file",
type
: "file",
extensions
: [".json", ".yaml", ".yml"],
includeHidden
: false,
description
:
message
`Configuration file`
}; }, }; }

The suggest() method receives the current input prefix and should yield Suggestion objects. The shell completion system handles filtering and display, while native file completion provides better performance and platform-specific behavior for file system operations.

Completion suggestions improve user experience by making CLI applications more discoverable and reducing typing errors, while maintaining the same type safety and validation guarantees as the parsing logic.