/**
* Browser-safe networking helpers built around {@link urlBuilder}: a
* tolerant `URL` coercion + chainable builder that gracefully handles
* partial inputs (bare hosts, path-only strings, `{ url }` wrappers),
* a small IPv4 / IPv6 address + CIDR toolkit (parsing and membership
* lookups), and email-address parsing ({@link parseEmails} /
* {@link isEmail}) that normalizes the CSV / single-string / array
* inputs email recipient and allow-list fields accept into a clean
* `string[]`. No node-only imports, so this module is the canonical
* home for anything URL-, IP-, or email-shaped that also has to run in
* a Vite / Webpack / esbuild client bundle. Node-only network helpers
* (e.g. DNS resolution) belong in a node-tagged package, not here.
* Reuse this module for URL, IP/CIDR, and email parsing instead of introducing
* package-local normalizers with subtly different rules.
*
* @module
*/
import type { NonFunctionKeys } from "./object.ts";
/** Highest valid TCP port number. */
export declare const MAX_TCP_PORT = 65535;
/**
* Anything {@link urlBuilder} knows how to coerce
* into a URL:
*
* - A string: a host, a full URL, or a path / query / hash fragment.
* - A WHATWG `URL` instance.
* - Any object with a `url` field (e.g. a fetch `Request`, a Databricks
* `WorkspaceClient` config).
*/
export type UrlLike = string | URL | {
url: string;
};
/** IP protocol family: `4` for IPv4, `6` for IPv6. */
export type IpVersion = 4 | 6;
/**
* A parsed IP address. `value` is the address as an unsigned integer
* (a 32-bit range for v4, 128-bit for v6) held in a `bigint` so both
* families share one comparison / masking path. Produce one with
* {@link parseIp}.
*/
export interface ParsedIp {
version: IpVersion;
/** The address as an unsigned integer (`bigint` for uniform v4/v6 math). */
value: bigint;
}
/**
* A parsed CIDR block (`10.0.0.0/8`, `2001:db8::/32`). `base` is the
* network address with all host bits cleared, so membership is a
* single masked compare (see {@link ipInCidr}). Produce one with
* {@link parseCidr}. Carries the normalized `cidr` string for logging
* and to key back to caller-side metadata.
*/
export interface Cidr {
version: IpVersion;
/** Network address (host bits zeroed) as an unsigned `bigint`. */
base: bigint;
/** Prefix length in bits (the number after the `/`). */
prefix: number;
/** Normalized `
/` string. */
cidr: string;
}
/** Settable, non-method `URL` properties - the keys {@link UrlBuilder.with} accepts. */
type UrlPropertyKey = NonFunctionKeys;
/**
* A `URL` subclass with chainable, copy-on-write helpers. Every mutating
* method returns a fresh builder rather than editing in place, so a base
* builder can be safely reused. Construct one via {@link urlBuilder}.
*/
declare class UrlBuilderImpl extends URL {
constructor(url: URL);
/** The scheme without the trailing colon (`https`, not `https:`). */
get scheme(): string;
set scheme(value: string);
/**
* Return a copy with a single `URL` property (`pathname`, `search`,
* `hostname`, `scheme`, ...) set to `value`, leaving this builder
* untouched.
*/
with(key: K, value: UrlBuilderImpl[K]): UrlBuilder;
/**
* Return a copy with `pathSegments` appended to the current pathname.
* Segments may be strings or string arrays; each is trimmed of
* boundary slashes and blanks are dropped before joining with `/`.
*/
withPathAppend(...pathSegments: (string | string[])[]): UrlBuilder;
/**
* Return a copy whose pathname is `pathSegments` joined with `/`,
* replacing any existing path. Segments may be strings or string
* arrays; each is trimmed of boundary slashes and blanks are dropped.
*/
withPathReplace(...pathSegments: (string | string[])[]): UrlBuilder;
/**
* Test whether this URL's pathname is `path` or lives beneath it,
* matching on segment boundaries so `/api` matches `/api` and
* `/api/cool` but not `/apicool`. A missing leading slash on `path`
* is tolerated; query and hash are ignored (they aren't part of
* `pathname`). Matching is exact otherwise - trailing slashes are
* not normalized and `/` matches only the root.
*/
pathMatches(path: string): boolean;
}
/** Public type for the {@link urlBuilder} return value. */
export type UrlBuilder = UrlBuilderImpl;
/** With no argument, resolves to the base origin (never `null`). */
export declare function urlBuilder(): UrlBuilder;
/**
* Coerce a {@link UrlLike} into a chainable {@link UrlBuilder}, or `null`
* when the input cannot be parsed into a URL. Never throws.
*
* - A `URL` instance or `{ url }` wrapper is adopted as-is.
* - A bare hostname (`"example.com"`) is upgraded to `https://`; an
* explicit scheme is preserved.
* - A path / query / hash fragment (`"/api"`, `"?q=1"`, `"#x"`) is
* resolved against {@link defaultUrl} (the browser origin, else
* `http://localhost`).
* - An empty / blank string or omitted input resolves to the base
* origin.
*
* @example
* urlBuilder("example.com"); // https://example.com/
* urlBuilder("http://x/path"); // http://x/path
* urlBuilder("/api/v2"); // http://localhost/api/v2
* urlBuilder({ url: "http://y" }); // http://y/
* urlBuilder(); // http://localhost/
*/
export declare function urlBuilder(input?: UrlLike): UrlBuilder | undefined;
/** Options for {@link parseEmails}. */
export interface ParseEmailsOptions {
/** Lower-case every address (useful for allow-lists / matching). Off by default. */
lowercase?: boolean;
/**
* Drop later case-insensitive duplicates, keeping first-seen casing.
* On by default - a recipient or pattern list rarely wants repeats.
*/
dedupe?: boolean;
}
/**
* Normalize the shapes an email address field accepts - a single string,
* a comma / semicolon / whitespace separated list, an array of either,
* or nothing - into a clean `string[]`. Each entry is split on the list
* separators, trimmed, and de-emptied; blanks and non-string array
* members are dropped. By default duplicates are removed
* case-insensitively (first-seen casing wins) and casing is otherwise
* preserved; pass `lowercase` to fold everything to lower case (e.g. for
* an allow-list). This is the one place recipient lists, CC/BCC inputs,
* and sender allow-lists across the repo agree on how free-text email
* input is read. Never throws; does not validate - pair with
* {@link isEmail} when you need to reject malformed addresses.
*
* @example
* parseEmails("a@x.com, b@y.com; a@x.com"); // ["a@x.com", "b@y.com"]
* parseEmails(["A@x.com", " b@y.com "]); // ["A@x.com", "b@y.com"]
* parseEmails("*@corp.com", { lowercase: true }); // ["*@corp.com"]
* parseEmails(undefined); // []
*/
export declare function parseEmails(input: string | readonly string[] | null | undefined, options?: ParseEmailsOptions): string[];
/**
* Pragmatic `local@domain.tld` shape check: non-empty local and domain
* parts around a single `@`, with a dotted domain and no whitespace.
* Deliberately loose (not full RFC 5322) - enough to catch obvious typos
* and give CLI / form feedback, not to gate delivery. Trims first. This
* is a purely syntactic test, so a wildcard allow-list pattern like
* `*@example.com` passes; distinguishing a pattern from a real address
* is the sender policy's job, not this validator's.
*
* @example
* isEmail("alice@example.com"); // true
* isEmail("nope"); // false (no @)
* isEmail("no@domain"); // false (no dotted domain)
*/
export declare function isEmail(value: string): boolean;
/**
* Parse an IPv4 (`"1.2.3.4"`) or IPv6 (`"2001:db8::1"`,
* `"::ffff:1.2.3.4"`, `"::1"`) address into a {@link ParsedIp}, or
* `null` when the input isn't a valid literal. Never throws. Surrounding
* whitespace, `[...]` brackets around an IPv6 literal, and an IPv6 zone
* id (`fe80::1%eth0`) are all tolerated. The `value` is returned as a
* `bigint` so v4 and v6 share one comparison path.
*
* @example
* parseIp("10.0.0.1"); // { version: 4, value: 167772161n }
* parseIp("2001:db8::1"); // { version: 6, value: ... }
* parseIp("not-an-ip"); // null
*/
export declare function parseIp(input: string): ParsedIp | null;
/**
* Parse a CIDR block (`"10.0.0.0/8"`, `"2001:db8::/32"`) into a
* {@link Cidr} with host bits cleared from `base`, or `null` when the
* input isn't a valid block (bad address, missing / out-of-range
* prefix). Never throws.
*
* @example
* parseCidr("10.0.0.0/8")?.base; // 167772160n (10.0.0.0)
* parseCidr("10.1.2.3/8")?.base; // 167772160n (host bits dropped)
* parseCidr("10.0.0.0/40"); // null (prefix > 32 for v4)
*/
export declare function parseCidr(input: string): Cidr | null;
/**
* Test whether `ip` falls inside `cidr`. Both arguments accept either
* a string (parsed on the fly via {@link parseIp} / {@link parseCidr})
* or an already-parsed value - pass parsed values in hot loops to skip
* re-parsing. Returns `false` for unparseable input or a version
* mismatch (an IPv4 address is never inside an IPv6 block).
*
* @example
* ipInCidr("10.1.2.3", "10.0.0.0/8"); // true
* ipInCidr("11.0.0.1", "10.0.0.0/8"); // false
*/
export declare function ipInCidr(ip: string | ParsedIp, cidr: string | Cidr): boolean;
/**
* Return the first CIDR in `cidrs` that contains `ip`, or `null` when
* none match. `cidrs` holds pre-parsed {@link Cidr} values (or any
* object extending it), so callers can carry side metadata on each
* entry (e.g. a region tag) and read it straight off the returned
* match. Linear scan; parse the address once by passing a
* {@link ParsedIp}.
*
* @example
* const ranges = ["10.0.0.0/8", "192.168.0.0/16"]
* .map(parseCidr)
* .filter((c): c is Cidr => c !== null);
* findContainingCidr("10.1.2.3", ranges)?.cidr; // "10.0.0.0/8"
*/
export declare function findContainingCidr(ip: string | ParsedIp, cidrs: Iterable): T | null;
/**
* Whether `input`'s host is a loopback address - `localhost`, an IPv4
* `127.0.0.0/8` address, or IPv6 `::1` - i.e. a service running on this
* machine (e.g. a local registry). Accepts anything {@link urlBuilder}
* coerces (a URL string, `URL`, or `{ url }`); returns `false` when the
* input has no resolvable host.
*
* @example
* isLoopbackHost("http://localhost:4873"); // true
* isLoopbackHost("http://127.0.0.1"); // true
* isLoopbackHost("https://registry.npmjs.org"); // false
*/
export declare function isLoopbackHost(input: UrlLike): boolean;
export {};