js/net/http/client
js/net/http/client.ts
fino:net/http/client — reusable HTTP clients and logical sessions.
This module provides a lower-level client API for code that needs more
control and observability than global fetch(). HttpClient owns reusable
policy such as a base URL, default headers, protocol preferences, and logical
sessions. HttpSession represents an origin-scoped relationship that can
survive transport replacement. HttpResponse wraps a Fetch-compatible
Response with request, session, connection, protocol, trailer, and timing
metadata.
The first implementation intentionally layers on Fino's existing fetch,
EventSource, and WebSocket transports. That preserves current redirect,
abort, decompression, integrity, referrer, TLS, and HTTP/2 pool behavior
while establishing the public client/session surface. HTTP/1.1 sessions are
logical policy containers and still use one connection per request. Explicit
HTTP/3 sessions keep one QUIC/H3 transport active until reconnect() or
close(). HTTP/2 and HTTP/3 WebSocket attempts reject with a clear Extended
CONNECT error until those transports support it.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({
baseUrl: 'https://api.example.com',
headers: { authorization: 'Bearer token' },
});
const response = await client.request('/users');
console.log(response.status, response.protocol, await response.json());
const fetchResponse = await client.fetch('/users');
const events = client.sse('/events');
const socket = await client.websocket('/chat', { protocols: ['chat.v1'] });
Learn more:
- Fetch: https://fetch.spec.whatwg.org/
- HTTP semantics: https://www.rfc-editor.org/rfc/rfc9110
- Server-Sent Events: https://html.spec.whatwg.org/multipage/server-sent-events.html
- WebSocket: https://www.rfc-editor.org/rfc/rfc6455
Types
type HttpClientProtocol = 'http/1.1' | 'h2' | 'h3'
Application-level protocol a client or session may speak.
'http/1.1' uses one connection per request and is the default. 'h2'
multiplexes over a shared HTTP/2 pool entry keyed by origin. 'h3' runs over
a persistent QUIC transport that survives until reconnect() or close().
The value pins how a session behaves; the actual protocol a given response was
served on is reported separately by HttpResponse.protocol, which may differ
if the peer negotiated down.
import type { HttpClientProtocol } from 'fino:net/http/client';
const preferred: readonly HttpClientProtocol[] = ['h3', 'h2', 'http/1.1'];
type HttpClientTransport = 'tcp' | 'tls' | 'quic'
Physical transport carrying a connection, reported by HttpConnectionInfo.
'tcp' is cleartext HTTP/1.1, 'tls' is HTTP/1.1 or HTTP/2 over TLS, and
'quic' is HTTP/3 over a QUIC datagram flow. The transport is derived from
the negotiated protocol and the request URL scheme, not chosen directly.
type HttpHeadersInit = Headers | string[][] | Record<string, string> | null | undefined
Header collection accepted anywhere the client takes headers.
Any form the Headers constructor understands is allowed: an existing
Headers instance, an array of [name, value] pairs, or a plain object.
null and undefined mean "no headers" so callers can pass an optional
value straight through without branching.
import type { HttpHeadersInit } from 'fino:net/http/client';
const a: HttpHeadersInit = { authorization: 'Bearer t0ken' };
const b: HttpHeadersInit = [['accept', 'application/json']];
const c: HttpHeadersInit = new Headers({ 'x-trace': 'abc' });
type HttpSessionEvent = {
type: 'connecting';
session: HttpSession;
} | {
type: 'connected';
session: HttpSession;
connection: HttpConnectionInfo;
} | {
type: 'reconnecting';
session: HttpSession;
reason: unknown;
} | {
type: 'reconnected';
session: HttpSession;
connection: HttpConnectionInfo;
} | {
type: 'closed';
session: HttpSession;
reason?: unknown;
}
Discriminated lifecycle event emitted by a logical HTTP session.
A session records these as it moves through its life: connecting and
connected (carrying the new HttpConnectionInfo) around establishing a
transport, reconnecting and reconnected around a transport replacement,
and closed on teardown. The HTTP/1.1 and HTTP/2 paths emit connected per
request since they do not hold a dedicated transport; HTTP/3 sessions emit
connected once when the QUIC transport comes up. Consume the stream via
HttpSession.events and switch on type.
for await (const event of session.events) {
switch (event.type) {
case 'connected':
console.log('up on', event.connection.protocol);
break;
case 'closed':
console.log('down:', event.reason);
break;
}
}
Interfaces
interface HttpClientOptions {
Configuration for new HttpClient().
Every field is optional; an empty object yields an HTTP/1.1 client with no
base URL and no default headers, so requests must pass absolute URLs. The
base URL resolves relative paths for request(), sse(), websocket(), and
webtransport(). Default headers are merged into every request and can be
overridden per call. protocols lists preferences in priority order — the
first entry drives explicit sessions and the default for request(). tls
is forwarded to the underlying fetch, EventSource, and QUIC transports.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({
baseUrl: 'https://api.example.com/v1',
headers: { authorization: 'Bearer t0ken', accept: 'application/json' },
protocols: ['h2', 'http/1.1'],
tls: { rejectUnauthorized: true },
});
Properties
baseUrl?: string | URL
Base URL used to resolve relative request, SSE, WebSocket, and WebTransport paths.
When omitted, calls that take paths must use absolute URLs.
headers?: HttpHeadersInit
Headers applied to every request unless overridden.
Per-request headers are merged on top with the same names replacing these defaults.
protocols?: readonly HttpClientProtocol[]
Preferred protocols, in priority order.
The first entry is used for request() and for sessions that do not pass an
explicit protocol. Defaults to ['http/1.1'].
tls?: {
ca?: string;
rejectUnauthorized?: boolean;
cert?: string;
key?: string;
}
TLS options forwarded to the underlying fetch, EventSource, and QUIC transports.
interface HttpRequestInit {
Per-request overrides for HttpClient.request() and the underlying send.
These mirror the familiar Fetch options — method, headers, body,
signal, redirect, integrity, and the referrer controls — and add
trailers (a Headers value or a function producing one, applied after the
body) plus transport-specific tls and quic overrides. Headers here are
merged on top of the client's defaults. tls/quic fall back to the
client's TLS options when omitted.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({ baseUrl: 'https://api.example.com' });
const controller = new AbortController();
const res = await client.request('/users', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name: 'Ada' }),
signal: controller.signal,
redirect: 'error',
});
Properties
method?: string
HTTP method to send. Defaults to GET.
headers?: HttpHeadersInit
Headers merged on top of the client's defaults for this request.
body?: unknown
Request body forwarded to the selected transport.
Streaming bodies are treated as non-replayable in HttpRequestInfo.
signal?: MinimalAbortSignal | null
Abort signal observed by the underlying transport.
redirect?: 'follow' | 'error' | 'manual'
Redirect mode forwarded to fetch-backed transports.
integrity?: string
Subresource integrity metadata forwarded to fetch-backed transports.
referrerPolicy?: 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url' | ''
Referrer policy forwarded to fetch-backed transports.
referrer?: string
Referrer value forwarded to fetch-backed transports.
trailers?: Headers | (() => Headers | Promise<Headers>)
Trailer headers, or a function producing trailer headers after the body.
tls?: HttpClientOptions['tls']
TLS overrides for this request.
quic?: H3FetchInit['quic']
QUIC overrides for HTTP/3 requests.
interface HttpSessionRequest extends HttpRequestInit {
Request descriptor accepted by HttpSession.request().
Extends HttpRequestInit with the target location, since a session already
knows its origin. Supply url (absolute or relative) or path (relative to
the session origin); url wins if both are present, and when neither is
given the request targets /.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient();
const session = await client.session('https://api.example.com');
const res = await session.request({ path: '/users/42', method: 'GET' });
Properties
path?: string | URL
Path or URL resolved against the session origin.
Ignored when url is also provided.
url?: string | URL
Absolute or relative URL to request.
Wins over path; defaults to / when both are omitted.
interface HttpSessionOptions {
Options for HttpClient.session().
protocol pins the returned session to a single protocol; when omitted the
session adopts the client's first configured protocol. Sessions are cached
per protocol:origin, so requesting the same origin with a different
protocol yields a distinct session.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({ protocols: ['h3'] });
const session = await client.session('https://api.example.com', { protocol: 'h3' });
Properties
protocol?: HttpClientProtocol
Pin the session to one protocol.
Defaults to the client's first configured protocol.
interface ReconnectOptions {
Options for HttpSession.reconnect().
reason is an opaque value recorded on the emitted reconnecting lifecycle
event so observers can attribute the transport replacement (for example, a
caught error or a rotation signal).
await session.reconnect({ reason: 'idle timeout' });
Properties
reason?: unknown
Opaque reason attached to the reconnecting lifecycle event.
interface CloseOptions {
Options for HttpClient.close() and HttpSession.close().
reason is an opaque value attached to the emitted closed lifecycle event,
useful for logging why a session was torn down.
await session.close({ reason: 'shutdown' });
Properties
reason?: unknown
Opaque reason attached to the closed lifecycle event.
interface HttpRequestInfo {
Snapshot of the request that produced an HttpResponse.
method and url are the final, normalized values actually sent. headers
is the merged header set after client defaults and per-request overrides.
idempotent reflects whether the method is safe to retry per HTTP semantics
(GET, HEAD, OPTIONS, TRACE, PUT, DELETE). replayable is true only when
the body can be re-sent — it is false for streaming bodies such as
ReadableStream or async iterables, which are consumed once. attempt is
the 1-based try count.
const res = await client.request('/users');
if (res.request.idempotent && res.request.replayable) {
// safe to retry this request
}
Readonly Properties
readonly method: string
Final uppercase HTTP method sent on the wire.
readonly url: string
Final absolute request URL.
readonly headers: Headers
Merged request headers after client defaults and per-call overrides.
readonly idempotent: boolean
Whether the method is considered idempotent for retry decisions.
readonly replayable: boolean
Whether the request body can be sent again.
readonly attempt: number
One-based attempt number for this send.
interface HttpConnectionInfo {
Metadata about the transport connection a response was served on.
id is a per-process synthetic connection identifier, distinct from the
logical HttpSession.id. protocol and transport describe what was
actually negotiated. localAddress/remoteAddress and alpnProtocol are
populated for HTTP/3 (QUIC) connections and left null for the HTTP/1.1 and
HTTP/2 paths, which layer on the shared fetch pool. connectedAt is a
Date.now() millisecond timestamp.
const res = await client.request('/users');
const conn = res.connection;
if (conn !== null) {
console.log(conn.protocol, conn.transport, conn.alpnProtocol);
}
Readonly Properties
readonly id: string
Synthetic per-process connection id.
readonly protocol: HttpClientProtocol
Negotiated application protocol for this connection.
readonly transport: HttpClientTransport
Physical transport used by the connection.
readonly localAddress: unknown | null
Local address when the transport exposes one, otherwise null.
readonly remoteAddress: unknown | null
Remote address when the transport exposes one, otherwise null.
readonly alpnProtocol: string | null
Negotiated ALPN protocol when available.
readonly connectedAt: number
Millisecond timestamp recorded when the connection metadata was created.
interface HttpResponseTiming {
Coarse timing marks captured while sending a request and reading its body.
All values are Date.now() millisecond timestamps. startTime is recorded
just before the request is sent. responseHeadersEnd is set once the status
line and headers arrive. bodyEnd is set when the response body is fully
consumed or the response is closed — so it stays undefined until you read
the body via text(), json(), bytes(), arrayBuffer(), iterate body,
or call close().
const res = await client.request('/users');
const body = await res.text();
const total = (res.timing.bodyEnd ?? Date.now()) - res.timing.startTime;
console.log(`request took ${total}ms`);
Readonly Properties
readonly startTime: number
Millisecond timestamp taken immediately before the request is sent.
readonly responseHeadersEnd?: number
Millisecond timestamp taken after response status and headers arrive.
readonly bodyEnd?: number
Millisecond timestamp set when the body is consumed or closed.
interface SseOptions extends EventSourceInit {
Options for HttpClient.sse() and HttpSession.sse().
Extends the standard EventSourceInit with a headers field so the stream
inherits and can extend the client's default headers (for example, an
authorization token). TLS falls back to the client's configuration when not
set on the options.
const events = client.sse('/events', {
headers: { 'last-event-id': '42' },
withCredentials: true,
});
events.addEventListener('message', (e) => console.log(e.data));
Properties
headers?: HttpHeadersInit
Headers merged with client or session defaults for the stream request.
interface HttpWebSocketOptions extends WebSocketConnectOptions {
Options for HttpClient.websocket() and HttpSession.websocket().
Extends the transport's WebSocketConnectOptions (subprotocols, and so on)
with extra headers merged into the upgrade request. WebSocket is only
available over HTTP/1.1; attempting it on an h2 or h3 session rejects,
because Extended CONNECT is not yet supported.
const socket = await client.websocket('/chat', {
protocols: ['chat.v1'],
headers: { authorization: 'Bearer t0ken' },
});
socket.send('hello');
Properties
headers?: Record<string, string> | Headers
Headers merged with client or session defaults for the WebSocket upgrade.
interface HttpWebTransportOptions extends WebTransportOptions {
Options for HttpClient.webtransport() and HttpSession.webtransport().
Extends WebTransportOptions with request headers plus tls/quic
overrides for the underlying QUIC connection. Any protocols are sent as the
sec-webtransport-protocol header. WebTransport requires an h3 session and
an https: URL.
const wt = await client.webtransport('https://api.example.com/wt', {
protocols: ['app.v1'],
});
await wt.ready;
Properties
headers?: Record<string, string> | Headers
Headers merged with client or session defaults for the CONNECT request.
tls?: HttpClientOptions['tls']
TLS overrides for the underlying HTTP/3 transport.
quic?: H3FetchInit['quic']
QUIC overrides for the underlying HTTP/3 transport.
Classes
class HttpResponse {
Response returned by HttpClient.request() and HttpSession.request().
Wraps a Fetch-compatible Response and augments it with the request that
produced it, the logical session, the transport connection, the actual
protocol served, incoming trailers, and coarse timing marks. The body
helpers (text(), json(), bytes(), arrayBuffer()) and the streaming
body iterable are single-consumption: the first that runs marks the body
consumed, and any later read — including toFetchResponse() — throws
TypeError('Body already consumed'). close() drains or cancels an unread
body without throwing so responses can be discarded safely.
Applications rarely construct this directly; obtain one from a request.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({ baseUrl: 'https://api.example.com' });
const res = await client.request('/users/42');
if (res.status === 200) {
const user = await res.json();
console.log(res.protocol, user);
} else {
await res.close();
}
Readonly Properties
readonly status: number
HTTP status code copied from the underlying response.
readonly statusText: string
HTTP reason phrase copied from the underlying response.
readonly headers: Headers
Response headers.
readonly url: string
Final response URL after redirect handling.
readonly redirected: boolean
Whether the underlying transport followed at least one redirect.
readonly request: HttpRequestInfo
Metadata for the request that produced this response.
readonly session: HttpSession
Logical session used for the request.
readonly connection: HttpConnectionInfo | null
Transport connection metadata, or null when unavailable.
readonly protocol: HttpClientProtocol
Protocol that served this response.
readonly trailers: Promise<Headers>
Incoming trailer headers.
The promise resolves after the response body has been read when the transport supports trailers.
readonly timing: HttpResponseTiming
Request/response timing metadata.
readonly body: AsyncIterable<Uint8Array> | null
Streaming response body.
Iterating this consumes the body and prevents later use of body helper
methods or toFetchResponse().
Constructors
constructor(init: {
response: Response;
request: HttpRequestInfo;
session: HttpSession;
connection: HttpConnectionInfo | null;
protocol: HttpClientProtocol;
timing: HttpResponseTiming;
markBodyEnd: () => void;
})
Wrap a Fetch-compatible Response together with client-level metadata.
Called by the client machinery; applications receive ready-made
HttpResponse values from request() and do not build them by hand.
Methods
async arrayBuffer(): Promise<ArrayBuffer>
Consume the body and return an ArrayBuffer.
async bytes(): Promise<Uint8Array>
Consume the body and return bytes.
async text(): Promise<string>
Consume the body and decode it as UTF-8 text.
async json(): Promise<unknown>
Consume the body and parse it as JSON.
Throws TypeError('Body already consumed') if any body helper, body
iteration, toFetchResponse(), or close() already ran, and rejects if the
payload is not valid JSON.
toFetchResponse(): Response
Hand back the underlying Fetch-compatible Response.
Marks this HttpResponse consumed and transfers body ownership to the
returned Response, so read the body through that object afterward. Throws
TypeError('Body already consumed') if the body was already read here.
async close(): Promise<void>
Release the response without reading it.
Marks the body consumed and cancels the underlying stream if it is still open. Safe to call more than once and never throws, making it the right way to discard a response you do not intend to read (for example, on a status you do not handle).
class HttpSession {
Logical, origin-scoped relationship with a server.
A session pins one origin and one protocol and carries the client's default
headers, giving a stable identity (id) that outlives any single transport
connection. For HTTP/1.1 and HTTP/2 it is a policy container — each request
still flows through the shared fetch pool — while an h3 session keeps one
QUIC/H3 transport alive across requests until reconnect() or close().
Sessions expose their lifecycle through the events async iterable and can
additionally open SSE streams, WebSockets, and WebTransport bound to the same
origin and headers.
Obtain sessions from HttpClient.session() rather than constructing them.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({ protocols: ['h3'] });
const session = await client.session('https://api.example.com', { protocol: 'h3' });
try {
const a = await session.request({ path: '/a' });
const b = await session.request({ path: '/b' });
console.log(a.session.id === b.session.id); // true — stable identity
} finally {
await session.close();
}
Readonly Properties
readonly id: string
Stable logical session identity.
This differs from HttpConnectionInfo.id; it remains stable across
reconnects and multiple requests on the same logical session.
readonly origin: string
Origin URL for the session, such as https://api.example.com.
readonly protocol: HttpClientProtocol
Protocol pinned for every request sent through this session.
Constructors
constructor(client: HttpClient, origin: URL, protocol: HttpClientProtocol, headers: Headers)
Bind a session to a client, origin, protocol, and default headers.
Applications should call HttpClient.session() instead, which caches and
reuses sessions per protocol:origin.
Getters
get state(): 'connecting' | 'ready' | 'draining' | 'closed'
Current lifecycle state.
ready sessions can send requests. closed sessions reject new requests.
connecting and draining are reserved lifecycle states for transport
transitions.
get currentConnection(): HttpConnectionInfo | null
Current transport connection, if one is active or last used.
HTTP/3 sessions keep this as the active QUIC connection. HTTP/1.1 and HTTP/2 sessions update it with the most recent request's connection metadata.
get events(): AsyncIterable<HttpSessionEvent>
Async iterable replaying the lifecycle events recorded so far.
Iterating yields the buffered HttpSessionEvent history and completes; it
is a snapshot, not a live subscription, so re-iterate to observe events
appended after the loop finished.
Methods
async _attachH3TransportForTest(conn: QuicConnection): Promise<void>
async _request(input: string | URL, init: HttpRequestInit = {}): Promise<HttpResponse>
request(init: HttpSessionRequest = {}): Promise<HttpResponse>
Send a request over this session and resolve to a rich HttpResponse.
Resolves the target from init.url or init.path against the session
origin (defaulting to /), merges the session's default headers, and sends
on the session's pinned protocol. Throws if the session has been closed.
sse(path: string | URL, options: SseOptions = {}): EventSource
Open an EventSource pinned to this session's origin and headers.
The returned EventSource uses merged session and per-call headers and
inherits the client's TLS policy unless options.tls overrides it.
async websocket(
path: string | URL,
options: HttpWebSocketOptions = {
}
): Promise<WebSocketConnection>
Open a WebSocket pinned to this session's origin and headers.
Rewrites the URL scheme to ws:/wss:, merges session headers into the
upgrade request, and resolves once the socket is open. Only HTTP/1.1
sessions are supported; an h2 or h3 session throws because WebSocket
over those protocols needs Extended CONNECT, which is not yet implemented.
async webtransport(path: string | URL, options: HttpWebTransportOptions = {}): Promise<WebTransport>
Open a WebTransport session over this session's QUIC transport.
Reuses the session's H3 transport (establishing it on first use), sends any
protocols as sec-webtransport-protocol, and resolves once the transport
is ready. Requires an h3 session and an https: URL; other protocols
throw.
async reconnect(options: ReconnectOptions = {}): Promise<void>
Drop the current transport while keeping the session's logical identity.
Emits a reconnecting event carrying options.reason, tears down any live
H3 transport, and clears the current connection so the next request
re-establishes one. The session id and origin are unchanged. Throws if the
session is already closed.
async close(options: CloseOptions = {}): Promise<void>
Close the session and release its transport.
Transitions to the closed state, tears down any H3 transport, evicts the
shared HTTP/2 pool entry for the origin when the session is h2, and emits
a final closed event with options.reason. Idempotent: closing an
already-closed session is a no-op. After closing, request() throws.
class HttpClient {
Reusable HTTP client that owns shared policy and logical sessions.
An HttpClient bundles a base URL, default headers, protocol preferences,
and TLS settings, then applies them across request(), fetch(), sse(),
websocket(), and webtransport(). It transparently manages a pool of
HttpSession objects keyed by protocol:origin, creating one on demand and
reusing it for subsequent requests to the same origin, which is what lets
HTTP/2 and HTTP/3 keep their multiplexed transports warm. Use it instead of
global fetch() when you want reuse, protocol control, or the request,
connection, and timing metadata carried on HttpResponse.
Close the client when done to release every session and pooled connection.
import { HttpClient } from 'fino:net/http/client';
const client = new HttpClient({
baseUrl: 'https://api.example.com',
headers: { authorization: 'Bearer t0ken' },
protocols: ['h2', 'http/1.1'],
});
try {
const res = await client.request('/users');
console.log(res.status, await res.json());
} finally {
await client.close();
}
Readonly Properties
readonly tls?: HttpClientOptions['tls']
TLS options inherited by requests, SSE helpers, and HTTP/3 sessions.
Constructors
constructor(options: HttpClientOptions = {})
Create a client with the given default policy.
All options are optional; the defaults are no base URL, no headers, and the
single protocol http/1.1. Throws TypeError if baseUrl is not an
http: or https: URL.
Methods
async request(input: string | URL | Request, init: HttpRequestInit = {}): Promise<HttpResponse>
Send a request and resolve to a rich HttpResponse.
Accepts a string, URL, or Fetch Request; a Request contributes its
method, headers, and body, which init can still override. The target is
resolved against the client base URL, routed through the pooled session for
its origin, and sent on the client's first protocol. Throws TypeError for
non-HTTP(S) URLs and throws if the client has been closed.
const res = await client.request('/users', { method: 'POST', body: '{}' });
console.log(res.status, res.protocol, res.connection?.transport);
async fetch(input: string | URL | Request, init: HttpRequestInit = {}): Promise<Response>
Send a request and return a plain Fetch-compatible Response.
A convenience wrapper over request() for code that only wants the standard
Response and none of the extra client metadata. The returned Response
owns the body, so read it from that object.
const res = await client.fetch('/health');
console.log(await res.text());
async session(origin: string | URL, options: HttpSessionOptions = {}): Promise<HttpSession>
Get the pooled logical session for an origin, creating it if needed.
The origin is resolved against the base URL and the session is cached per
protocol:origin; passing an explicit options.protocol selects (and keys)
a distinct session. Reuse the returned session to send correlated requests
and to observe lifecycle events. Throws for non-HTTP(S) URLs or if the
client is closed.
sse(input: string | URL, options: SseOptions = {}): EventSource
Open an EventSource using this client's base URL and default headers.
The stream is not pooled as an HttpSession, but it shares client headers
and TLS options.
async websocket(
input: string | URL,
options: HttpWebSocketOptions = {
}
): Promise<WebSocketConnection>
Open a WebSocket using this client's base URL and default headers.
Resolves once the socket reaches OPEN. Client-level WebSockets use the
HTTP/1.1 WebSocket transport; use session helpers when protocol pinning is
important.
async webtransport(
input: string | URL,
options: HttpWebTransportOptions = {
}
): Promise<WebTransport>
Open a WebTransport session using this client's base URL and default headers.
This uses or creates an HTTP/3 session for the target origin and rejects non-HTTPS URLs.
async closeIdleSessions(): Promise<void>
Close pooled sessions and drop them from the cache.
Currently closes every logical session the client holds, releasing their transports. The client itself stays usable and will lazily recreate sessions on the next request.
async close(): Promise<void>
Close the client and every session it owns.
Idempotent. After closing, request(), session(), and the SSE/WebSocket/
WebTransport helpers throw. Always call this when finished to avoid leaking
pooled HTTP/2 and HTTP/3 connections.