API Reference
Name resolution, one address per call. The only item here that works without a socket. An IP literal is parsed and returned without touching the network, so it costs nothing. A hostname goes to a short-lived worker thread, which keeps the reactor and the calling thread free while `getaddrinfo` runs. The calling thread still parks until the lookup finishes and there is no timeout, so a slow resolver holds the caller for as long as the system takes, retries included. One call returns one address, and when a name has both IPv4 and IPv6 results the IPv4 address wins. There is no cache, no TTL, no way to ask for a particular address family, and no reverse, SRV, MX, or TXT lookup: every call starts a fresh worker thread.
fn lookup(host: String): Promise<String>Resolve a hostname or IP literal to one IP address. A literal comes back in its normalized form, so `"0:0:0:0:0:0:0:1"` resolves to `"::1"`. A name is looked up afresh on a worker thread.
host — IP literal (`"127.0.0.1"`, `"::1"`) or DNS name (`"localhost"`, `"example.com"`).
returns — promise for one IP address as text, already settled when the call returns. It rejects with a message starting `DNS resolution failed for host` when the name has no address.
import { Dns } from "@std/net";
print(await Dns.lookup("127.0.0.1"));
print(await Dns.lookup("::1"));
print(await Dns.lookup("localhost"));A bound socket that hands out connected `TcpStream`s. Bind first, then hand the listener to whatever accepts connections: `bind()` and `port()` are the only calls that work before the first peer arrives, and `accept()` is the one that waits. Bind follows the platform defaults of the runtime, so there is nothing to configure: an address-reuse flag is set on Unix, which lets you rebind a port a previous socket left behind, a port another process is listening on still fails, and the accept backlog is fixed rather than settable. The usual server shape is: bind port 0, read `port()` to learn where the OS put the socket, pass the port to whatever client runs, then loop on `accept()` and hand each stream to a worker. Bind port 0 rather than a fixed port when several instances may run at once, and remember that `accept()` parks the thread that calls it.
init(handle: Int)
fields
- handle: Int
fn accept(): Promise<TcpStream>Wait for the next peer and wrap it in a stream. A connection already waiting in the backlog is taken at once; otherwise the call parks in the reactor until one arrives, and the calling thread parks with it. There is no timeout, so a parked `accept()` waits for a peer that may never come: the way out is to close the listener from another thread, which makes the call reject.
returns — promise for the accepted stream, already settled when the call returns. It is a fresh non-blocking socket with the same one reader and one writer rule as any other stream. It rejects with `accept failed: ...` when the listener is closed while the call waits, and when the OS refuses the connection.
fn bind(host: String, port: Int): TcpListenerBind a listening socket. Resolution works as it does in `TcpStream.connect()`: an IP literal is used as written and a hostname is resolved on the calling thread, with IPv4 preferred when a name has both families.
host — interface address to bind. `"127.0.0.1"` and `"::1"` are the loopback addresses a test server wants.
port — TCP port, 0 to 65535. Port `0` asks the OS for an ephemeral port, which `port()` reads back.
returns — the bound listener, directly rather than as a promise, because binding does not wait.
throws — `TcpListener.bind failed: ...` when the address cannot be resolved, the port is out of range, or the OS refuses the bind. The thrown value is the message string itself.
fn close()Close the listening socket and release the handle. A parked `accept()` rejects. Connections that were already accepted are untouched: each one is its own stream with its own handle, and closing the listener does not close them.
fn port(): IntPort this listener is bound to. Reading the port back is the only way to find out which port an ephemeral bind landed on, and it is worth doing before the first peer is accepted.
returns — bound port, or -1 when the listener is closed or the handle is not one.
A connected TCP socket. Both peers look the same: the client gets one from `TcpStream.connect()`, the server from `TcpListener.accept()`. A read parks until at least one byte arrives and returns whatever the socket had, so the byte count is whatever arrived rather than what you asked for. TCP carries a byte stream, not messages: framing is the protocol's job, and a caller that needs a length prefix or a delimiter has to parse it out of these arrays itself.
init(handle: Int)
fields
- handle: Int
fn close()Close the socket and release the handle. The reactor deregisters the socket and fails every queued or parked operation that names it, so a thread blocked in `read()` or `write()` rejects instead of waiting forever. Later operations on the stream reject too, and closing twice does nothing.
fn connect(host: String, port: Int): Promise<TcpStream>Open a connection to host and port. The address is resolved before the socket starts. An IP literal is used as written, and a hostname goes through the system resolver on the calling thread, which blocks until the lookup returns; when a name has both IPv4 and IPv6 results the IPv4 address wins. `Dns.lookup()` is the way to run that lookup on a worker thread instead. The connect itself runs in the reactor and the calling thread parks until the kernel reports the connection finished. There is no timeout, so a host that accepts the TCP handshake and then stalls parks the caller indefinitely.
host — hostname or IP literal to connect to.
port — TCP port, 0 to 65535. Anything outside that range fails as an invalid port.
returns — promise for the connected stream, already settled when the call returns. It rejects with `connect failed: ...` when the address cannot be resolved, the port is out of range, the socket cannot be created, or the kernel reports a connection error, and with `connect refused: ...` when the socket reports an error that landed after the reactor already accepted the connection.
fn handleOf(): IntThe reactor handle behind this stream. `TlsStream.connect()` needs the handle to move a connected stream into a TLS session, and a thread that speaks the raw `__rnx_net_*` builtins takes it as the socket argument. It means nothing outside the process that opened the socket.
returns — handle number, the same one the constructor was given.
import { TcpStream } from "@std/net";
let wrapped = new TcpStream(5);
print(wrapped.handleOf());fn read(max_bytes: Int): Promise<Array<Int>>Read up to max_bytes bytes, parking until at least one arrives. The cap is clamped into 1 to 65536, so `read(0)` still reads a single byte and a larger cap never allocates more than 64 KiB. The result is whatever the socket had, which can be short, and an empty array means the peer closed its end. End of stream stays readable, so later reads keep returning empty arrays instead of rejecting; only `close()` makes the stream unusable.
max_bytes — read cap in bytes; clamped to 1..65536.
returns — promise for the bytes read, each 0..255, or an empty array at end of stream. It rejects with `recv failed: ...` on a socket error, and with an empty detail after `close()`.
fn write(bytes: Array<Int>): Promise<Int>Send every byte of the payload, parking under backpressure. Bytes leave one at a time, each one its own reactor round trip, so a full send buffer parks the calling thread until the peer drains it. Each value is truncated to its low 8 bits before it goes out, so 300 travels as 44.
bytes — payload bytes, each truncated to 8 bits.
returns — promise for the number of bytes sent, which on success is always `bytes.length()`. It rejects with `send failed: ...` on a socket error, and with an empty detail after `close()`.
A TLS client session layered over a connected `TcpStream`. `TlsStream.connect()` takes over the TCP socket rather than wrapping it: the handle moves into the session and stops belonging to the stream you passed in, so that stream must not be used again, not even to close it. Handshake, reads, and writes all run in the reactor and park the calling thread the same way they do on a plain stream, and the same one reader and one writer rule applies. Certificates are verified against the Mozilla root set that ships with the runtime, and there is no way to skip that check or to add a private authority from Rasmalai code. Pointing `RNX_TEST_TLS_CA_DER` at a DER-encoded CA file replaces the root set with that one certificate, which is how a test reaches a local server with a self-signed certificate; the root store is built once per process, so the variable has to be set before the first TLS call. Client certificates are not supported.
init(handle: Int)
fields
- handle: Int
fn close()Send `close_notify`, close the socket, and release the handle. The alert is queued on the session and the socket is then dropped without a final flush, so a peer may never see it: treat a clean shutdown as best effort, not as a guarantee. Parked operations on this session reject, later ones reject as well, and closing twice does nothing.
fn connect(stream: TcpStream, domain: String): Promise<TlsStream>Upgrade a connected stream to TLS. `domain` is the name the certificate has to match. A DNS name is sent as SNI as well; an IP literal is not sent as SNI, and a server presenting a certificate for it has to carry a matching IP subject alternative name. The handshake runs in the reactor and the calling thread parks until it finishes. A peer that answers part of the handshake and then goes quiet parks the caller indefinitely.
stream — connected TCP stream. It is consumed: the socket moves into the session and the stream object is dead afterwards, whether the handshake succeeded or failed.
domain — name to verify the certificate against, normally the same host `TcpStream.connect()` was given.
returns — promise for the ready session, already settled when the call returns. It rejects with `tls init failed: ...` when the stream handle is unknown or already closed, or when `domain` is neither a DNS name nor an IP literal, and with `tls handshake failed: ...` when the peer closes during the handshake, the protocols do not meet, or the certificate does not verify against the root set.
fn handleOf(): IntThe reactor handle behind this session.
returns — session handle number, the same one the constructor was given.
import { TlsStream } from "@std/net";
let wrapped = new TlsStream(4);
print(wrapped.handleOf());fn read(max_bytes: Int): Promise<Array<Int>>Read up to max_bytes decrypted bytes, parking until at least one arrives. The cap is clamped into 1 to 65536, as on a plain stream, and the result is plaintext: record boundaries, padding, and the certificate exchange have already been removed, so what arrives is whatever the peer wrote through its TLS session. An empty array means the peer closed its end.
max_bytes — read cap in bytes; clamped to 1..65536.
returns — promise for the plaintext bytes read, each 0..255, or an empty array at end of stream. It rejects with `tls recv failed: ...` on a session error, and with an empty detail after `close()`.
fn write(bytes: Array<Int>): Promise<Int>Send every byte of the payload through the session, parking under backpressure. Bytes leave one at a time, each one encrypted, flushed, and handed to the reactor on its own, and each value is truncated to its low 8 bits before it goes out.
bytes — payload bytes, each truncated to 8 bits.
returns — promise for the number of bytes sent, which on success is always `bytes.length()`. It rejects with `tls send failed: ...` on a session error, and with an empty detail after `close()`.