-
Notifications
You must be signed in to change notification settings - Fork 2
Compat Net
Cross-runtime networking utilities with a unified API.
The Net module provides a unified interface for networking operations across Deno, Bun, and Node.js runtimes, including TCP listeners, connections, and hostname resolution.
- Cross-runtime compatibility - Works seamlessly across Deno, Bun, and Node.js
- TCP listeners - Create TCP servers on specified ports
- TCP connections - Connect to remote hosts via TCP
- Hostname resolution - Get the system hostname
- Type-safe - Full TypeScript support with detailed type definitions
| Feature | Bun | Deno | Node.js |
|---|---|---|---|
| TCP listener | ✅ | ✅ | ✅ |
| TLS listener | ✅ | ✅ | ✅ |
| Unix socket listen | ✅ | ✅ | ✅ |
| Accept connections | ✅ | ✅ | ✅ |
| TCP connection | ✅ | ✅ | ✅ |
| TLS connection | ✅ | ✅ | ✅ |
| Unix socket connect | ✅ | ✅ | ✅ |
| Connection timeout | ✅ | ✅ | ✅ |
| Abort signal support | ✅ | ✅ | ✅ |
| Upgrade TCP→TLS (STARTTLS) | ✅ | ✅ | ✅ |
rejectUnauthorized: false |
✅ | ❌* | ✅ |
| Hostname resolution | ✅ | ✅ | ✅ |
| Read from socket | ✅ | ✅ | ✅ |
| Write to socket | ✅ | ✅ | ✅ |
| Address info | ✅† | ✅† | ✅† |
*Deno requires the --unsafely-ignore-certificate-errors=hostname CLI flag instead.
†TCP/TLS only. A UNIX-socket Connection — from either listen() or
connect() — always has remoteAddr: undefined and localAddr: undefined, on every runtime; UNIX sockets have no host/port to report.
Deno:
deno add @tundralibs/compatBun:
bunx jsr add @tundralibs/compatNode.js:
npx jsr add @tundralibs/compatimport { connect, hostname, listen } from '@tundralibs/compat/net';Direct import (Deno):
import { connect, hostname, listen } from 'jsr:@tundralibs/compat/net';Options for creating a TCP, TLS, or Unix socket listener.
type ListenOptions =
| {
/** The port number to listen on */
port: number;
/** The hostname to bind to (default: "0.0.0.0") */
hostname?: string;
/** TLS configuration for secure connections */
tls?: boolean | TLSOptions;
/** AbortSignal to close the listener automatically */
signal?: AbortSignal;
}
| {
/** The Unix socket path to listen on */
path: string;
/** AbortSignal to close the listener automatically */
signal?: AbortSignal;
};TCP/TLS Properties:
-
port- The port number to listen on (required) -
hostname- The hostname to bind to (optional, defaults to"0.0.0.0") -
tls- TLS configuration (optional). Usetruefor TLS without client cert validation, or provideTLSOptionsfor full TLS configuration -
signal- AbortSignal to automatically close the listener when aborted (optional)
Unix Socket Properties:
-
path- The Unix socket path to listen on (required) -
signal- AbortSignal to automatically close the listener when aborted (optional)
Options for creating a TCP, TLS, or Unix socket connection.
type ConnectOptions =
| {
/** The port number to connect to */
port: number;
/** The hostname to connect to (default: "127.0.0.1") */
hostname?: string;
/** TLS configuration for secure connections */
tls?: boolean | TLSOptions;
/** Connection timeout in milliseconds */
timeout?: number;
/** AbortSignal to cancel the connection attempt */
signal?: AbortSignal;
}
| {
/** The Unix socket path to connect to */
path: string;
/** Connection timeout in milliseconds */
timeout?: number;
/** AbortSignal to cancel the connection attempt */
signal?: AbortSignal;
};TCP/TLS Connection Properties:
-
port- The port number to connect to (required) -
hostname- The hostname to connect to (optional, defaults to"127.0.0.1") -
tls- TLS configuration (optional). Usetruefor system trust roots with no client cert, or aTLSOptionsobject for custom CA or mTLS. -
timeout- Connection timeout in milliseconds (optional) -
signal- AbortSignal for manual cancellation (optional)
Unix Socket Properties:
-
path- The Unix socket path to connect to (not supported on Windows) -
timeout- Connection timeout in milliseconds (optional) -
signal- AbortSignal for manual cancellation (optional)
Note: When both timeout and signal are provided, the connection aborts when either triggers first.
A cross-runtime listener type for TCP, TLS, or Unix sockets.
type Listener = {
/** Accepts an incoming connection */
accept(): Promise<Connection>;
/** Closes the listener and releases the port/socket */
close(): void;
};Methods:
-
accept()- Waits for and accepts an incoming connection. Returns aPromisethat resolves with aConnectionobject. This can be called repeatedly to accept multiple connections. -
close()- Closes the listener and releases the port or Unix socket. Safe to call multiple times.
A cross-runtime TCP connection type providing read/write capabilities.
type Connection = {
/** Reads data from the connection */
read(): Promise<Uint8Array | null>;
/** Writes data to the connection */
write(data: Uint8Array | string): Promise<number>;
/** Closes the connection */
close(): void;
/** The remote address information */
readonly remoteAddr?: {
hostname: string;
port: number;
};
/** The local address information */
readonly localAddr?: {
hostname: string;
port: number;
};
};Methods:
-
read()- Reads data from the connection. Returns binary data as aUint8Array, ornullif the connection has been closed (EOF). TCP does not preserve message boundaries, so data may be partial, complete, or multiple messages. -
write(data)- Writes data to the connection. Accepts either aUint8Arrayor astring. Returns the number of bytes written. -
close()- Closes the connection. Safe to call multiple times.
Properties:
-
remoteAddr- Information about the remote endpoint (hostname and port).undefinedfor a UNIX-socket connection on every runtime — there's no host/port to report. -
localAddr- Information about the local endpoint (hostname and port). Same UNIX-socket caveat asremoteAddr. -
_raw- The underlying runtime-specific socket handle. Used internally byupgradeTls()to perform in-place TLS negotiation (e.g. PostgresSSLRequest, SMTPSTARTTLS). Treat as opaque — do not call methods on this object directly.
Options for upgrading a plain TCP connection to TLS.
type UpgradeTlsOptions = {
/** Hostname for SAN/SNI certificate verification (required). */
hostname: string;
/**
* TLS configuration. Same shape as ConnectOptions.tls.
* Use `true` for system trust roots, or TLSOptions for custom CA / mTLS.
*/
tls?: boolean | TLSOptions;
};Creates a TCP, TLS, or Unix socket listener.
async function listen(options: ListenOptions): Promise<Listener>;Parameters:
-
options- Listener configuration options- For TCP/TLS:
{ port, hostname?, tls? } - For Unix socket:
{ path }
- For TCP/TLS:
Returns: A promise resolving to a Listener object with accept() and close() methods
Throws:
-
Error- If the port is already in use or if binding fails -
FetchTLSError- Iftlsmixes the inline (cert/key/ca) and file-path (certFile/keyFile/caFile) styles -
FetchPathTraversalError- If TLS file paths contain traversal sequences -
FetchFileNotFoundError- If TLS certificate/key files don't exist -
FetchInvalidPEMError- If TLS certificates are not valid PEM format -
UnsupportedRuntimeError- If called in an unsupported runtime (on Workers, always — there is no way to accept an inbound connection)
Runtime Implementation:
-
Deno: Uses
Deno.listen()for TCP/Unix,Deno.listenTls()for TLS -
Bun: Uses Node.js-compatible
net.createServer()for TCP/Unix,tls.createServer()for TLS -
Node.js: Uses
net.createServer()for TCP/Unix,tls.createServer()for TLS -
Cloudflare Workers: Throws
UnsupportedRuntimeError. Not a gap in compat — workerd's request/response model has no way to accept an inbound TCP connection, so there is nothing to bind. Outbound connections do work; seeconnect().
Example - Basic TCP server:
import { listen } from '@tundralibs/compat/net';
const listener = await listen({ port: 8080 });
console.log('Server listening on port 8080');
// Accept and handle connections
while (true) {
const conn = await listener.accept();
// Handle connection...
const data = await conn.read();
if (data) {
await conn.write('Hello from server!\n');
}
conn.close();
}Example - TLS server with file-based certificates:
import { listen } from '@tundralibs/compat/net';
const listener = await listen({
port: 8443,
hostname: '0.0.0.0',
tls: {
certFile: '/path/to/server.crt',
keyFile: '/path/to/server.key',
caFile: '/path/to/ca.crt', // optional
},
});
const conn = await listener.accept();
console.log('Secure connection accepted');
conn.close();
listener.close();Example - TLS server with string-based certificates:
import { listen } from '@tundralibs/compat/net';
const listener = await listen({
port: 8443,
tls: {
cert: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
key: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----',
},
});
const conn = await listener.accept();
conn.close();
listener.close();Example - Unix socket server:
import { listen } from '@tundralibs/compat/net';
const listener = await listen({ path: '/tmp/myapp.sock' });
console.log('Server listening on Unix socket');
const conn = await listener.accept();
const data = await conn.read();
if (data) {
console.log('Received:', new TextDecoder().decode(data));
}
conn.close();
listener.close();Example - Listener with graceful shutdown:
import { listen } from '@tundralibs/compat/net';
const controller = new AbortController();
const listener = await listen({
port: 8080,
signal: controller.signal,
});
console.log('Server listening on port 8080');
// Later, for graceful shutdown
controller.abort(); // Automatically closes the listener
console.log('Server shut down gracefully');Example - Testing port availability:
import { listen } from '@tundralibs/compat/net';
async function isPortAvailable(port: number): Promise<boolean> {
try {
const listener = await listen({ port });
listener.close();
return true;
} catch {
return false;
}
}
if (await isPortAvailable(8080)) {
console.log('Port 8080 is available');
} else {
console.log('Port 8080 is already in use');
}Creates a TCP or Unix socket connection to the specified destination.
async function connect(options: ConnectOptions): Promise<Connection>;Parameters:
-
options- Connection configuration options-
For TCP:
{ port, hostname? } -
For Unix socket:
{ path }
-
For TCP:
Returns: A promise that resolves to a Connection object
Throws:
-
Error- If the connection fails. On Deno specifically, also thrown (plainError, not aCompatErrorsubclass) whentls.rejectUnauthorizedisfalse— Deno has no in-process verification bypass; run with--unsafely-ignore-certificate-errorsor supplytls.ca/tls.caFileinstead. Bun and Node honor the flag. -
ConnectionTimeoutError- If the connection times out (whentimeoutis specified) -
FetchTLSError- Iftlsmixes the inline (cert/key/ca) and file-path (certFile/keyFile/caFile) styles -
FetchPathTraversalError- If TLS file paths contain traversal sequences -
FetchFileNotFoundError- If TLS certificate/key files don't exist -
FetchInvalidPEMError- If TLS certificates are not valid PEM format -
UnsupportedRuntimeError- If called in an unsupported runtime, or on Workers for a UNIX-socketpathor TLS materialcloudflare:socketscan't honour (see the Workers notes below)
Runtime Implementation:
-
Deno: Uses
Deno.connect()for TCP/Unix,Deno.connectTls()for TLS -
Bun: Uses
net.createConnection()for TCP/Unix,tls.connect()for TLS -
Node.js: Uses
net.createConnection()for TCP/Unix,tls.connect()for TLS -
Cloudflare Workers: Uses
cloudflare:sockets'connect()— the same primitive Hyperdrive is built on. Plain TCP opens withsecureTransport: 'starttls'soupgradeTls()stays available (the socket is still in the clear until you call it);tlsopens withsecureTransport: 'on'. Three things workerd cannot do, each anUnsupportedRuntimeErrorrather than a silent drop:-
UNIX sockets —
cloudflare:socketsdials TCP only. -
TLS material —
cert,key,caand their*Fileforms all throw, as doesrejectUnauthorized: false. workerd'sSocketOptionshas nowhere to put certificates and verifies the peer against its own trust store with no bypass, so the server needs a publicly-trusted certificate. For a private CA, route through Hyperdrive. -
timeout/signalare honoured, but by racing the abort against the socket opening and closing it —cloudflare:socketshas no signal parameter of its own.
-
UNIX sockets —
Example - TCP connection:
import { connect } from '@tundralibs/compat/net';
const conn = await connect({ hostname: 'example.com', port: 80 });
// Send HTTP request
await conn.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
// Read response
const data = await conn.read();
if (data) {
const response = new TextDecoder().decode(data);
console.log(response);
}
conn.close();Example - TCP connection with timeout:
import { connect } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
try {
const conn = await connect({
hostname: 'example.com',
port: 80,
timeout: 5000, // 5 second timeout
});
await conn.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
const data = await conn.read();
conn.close();
} catch (err) {
if (err instanceof ConnectionTimeoutError) {
console.error('Connection timed out after 5 seconds');
} else {
console.error('Connection failed:', err);
}
}Example - Abort connection manually:
import { connect } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
const controller = new AbortController();
// Cancel connection after 3 seconds
setTimeout(() => controller.abort(), 3000);
try {
const conn = await connect({
hostname: 'example.com',
port: 80,
signal: controller.signal,
});
conn.close();
} catch (err) {
if (err instanceof ConnectionTimeoutError) {
console.error('Connection was cancelled');
}
}Example - Combine timeout and signal:
import { connect } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
const controller = new AbortController();
// Whichever happens first will abort the connection
try {
const conn = await connect({
hostname: 'example.com',
port: 80,
timeout: 10000, // 10 second timeout
signal: controller.signal, // OR manual abort
});
// Use connection...
conn.close();
} catch (err) {
if (err instanceof ConnectionTimeoutError) {
console.error('Connection aborted (timeout or signal)');
}
}
// Later, can manually abort if needed
controller.abort();Example - TLS connection (file-based):
import { connect } from '@tundralibs/compat/net';
// Secure connection with client certificates
const conn = await connect({
hostname: 'secure.example.com',
port: 443,
tls: {
certFile: '/path/to/client.crt',
keyFile: '/path/to/client.key',
caFile: '/path/to/ca.crt', // optional
},
});
await conn.write('GET /api/data HTTP/1.1\r\nHost: secure.example.com\r\n\r\n');
const data = await conn.read();
conn.close();Example - TLS connection (string-based):
import { connect } from '@tundralibs/compat/net';
// Using certificate content directly
const conn = await connect({
hostname: 'api.example.com',
port: 8443,
tls: {
cert: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
key: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----',
},
});
conn.close();Example - Unix socket connection:
import { connect } from '@tundralibs/compat/net';
// Connect to a Unix domain socket
const conn = await connect({ path: '/tmp/app.sock' });
await conn.write('Hello via Unix socket\n');
const data = await conn.read();
if (data) {
const response = new TextDecoder().decode(data);
console.log('Response:', response);
}
conn.close();Example - Connect to localhost:
import { connect } from '@tundralibs/compat/net';
// Connects to localhost:8080
const conn = await connect({ port: 8080 });
await conn.write('Hello, server!\n');
conn.close();Example - Echo client:
import { connect } from '@tundralibs/compat/net';
async function echoClient(message: string) {
const conn = await connect({ hostname: 'localhost', port: 8080 });
// Write message
await conn.write(message);
// Read echo
const echo = await conn.read();
if (echo !== null) {
const decoder = new TextDecoder();
const response = decoder.decode(echo);
console.log('Server echoed:', response);
}
conn.close();
}
await echoClient('Hello, World!');Example - Check connection info:
import { connect } from '@tundralibs/compat/net';
const conn = await connect({ hostname: 'example.com', port: 80 });
console.log('Connected to:', conn.remoteAddr);
// Output: Connected to: { hostname: 'example.com', port: 80 }
console.log('Local address:', conn.localAddr);
// Output: Local address: { hostname: '192.168.1.100', port: 54321 }
conn.close();Upgrades a plain TCP Connection to TLS in place. Used by protocols that negotiate TLS after an initial plaintext exchange — for example:
-
PostgreSQL: client sends
SSLRequest, server replies'S', then TLS is negotiated on the same socket. -
SMTP: client sends
STARTTLS, server replies220, then TLS is negotiated.
The original Connection should be considered consumed after a successful upgrade — do not call read/write on it. Use the returned connection instead.
async function upgradeTls(
conn: Connection,
options: UpgradeTlsOptions,
): Promise<Connection>;Parameters:
-
conn- The plain TCP connection to upgrade. Must have been created byconnect()(not user-constructed), as it requires the_rawsocket handle. -
options.hostname- Hostname for SNI and server certificate SAN verification (required). -
options.tls- TLS configuration (optional). Same asConnectOptions.tls.
Returns: A new TLS-wrapped Connection.
Throws:
-
Error- Ifconn._rawis missing (connection was not created byconnect()). -
Error- On Deno, ifrejectUnauthorized: falseis requested (not supported — use--unsafely-ignore-certificate-errorsinstead). -
FetchTLSError- Iftlsmixes the inline (cert/key/ca) and file-path (certFile/keyFile/caFile) styles. -
FetchPathTraversalError- If TLS file paths contain traversal sequences. -
FetchFileNotFoundError- If TLS certificate/key files don't exist. -
FetchInvalidPEMError- If TLS certificates are not valid PEM format. -
UnsupportedRuntimeError- On an unrecognized runtime, and on Workers when the socket didn't come fromconnect(), whenhostnamediffers from the oneconnect()dialed, or for TLS material workerd can't honour.
Runtime Implementation:
-
Deno: Uses
Deno.startTls() -
Bun / Node.js: Uses
tls.connect({ socket: rawSocket, ... }) -
Cloudflare Workers: Uses the socket's own
startTls(). The same no-TLS-material rule asconnect()applies, plus two workerd-specific constraints:- The connection must have come from
connect()— workerd only upgrades a socket it opened withsecureTransport: 'starttls'. -
hostnamemust match the oneconnect()dialed.startTls()takes no arguments (itsexpectedServerHostnameoption is declared in@cloudflare/workers-typesbut rejected by the runtime), so workerd verifies against the dialed name. Rather than verify a different name than you asked for, a mismatch throwsUnsupportedRuntimeError.
- The connection must have come from
Example — PostgreSQL-style STARTTLS:
import { connect, upgradeTls } from '@tundralibs/compat/net';
// 1. Open plain TCP connection
const conn = await connect({ hostname: 'db.example.com', port: 5432 });
// 2. Send SSLRequest (PostgreSQL protocol)
const sslRequest = new Uint8Array([0, 0, 0, 8, 4, 210, 22, 47]);
await conn.write(sslRequest);
// 3. Read server response
const response = await conn.read();
if (!response || String.fromCharCode(response[0]) !== 'S') {
throw new Error('Server does not support SSL');
}
// 4. Upgrade to TLS (conn is consumed, use tlsConn from here on)
const tlsConn = await upgradeTls(conn, {
hostname: 'db.example.com',
tls: { caFile: '/etc/ssl/postgresql-ca.crt' },
});
// 5. Continue with TLS-encrypted communication
await tlsConn.write(new Uint8Array([/* startup message */]));
const data = await tlsConn.read();
tlsConn.close();Example — SMTP STARTTLS:
import { connect, upgradeTls } from '@tundralibs/compat/net';
const conn = await connect({ hostname: 'mail.example.com', port: 587 });
// Read SMTP greeting
await conn.read();
// Send EHLO
await conn.write('EHLO client.example.com\r\n');
await conn.read();
// Start TLS negotiation
await conn.write('STARTTLS\r\n');
const reply = await conn.read();
if (reply && new TextDecoder().decode(reply).startsWith('220')) {
// Server is ready for TLS — upgrade the connection
const tlsConn = await upgradeTls(conn, {
hostname: 'mail.example.com',
tls: true, // use system trust roots
});
// Continue SMTP over TLS
await tlsConn.write('EHLO client.example.com\r\n');
tlsConn.close();
}Example — mTLS upgrade:
import { type Connection, upgradeTls } from '@tundralibs/compat/net';
declare const conn: Connection;
const tlsConn = await upgradeTls(conn, {
hostname: 'secure.internal.corp',
tls: {
certFile: '/etc/ssl/client.crt',
keyFile: '/etc/ssl/client.key',
caFile: '/etc/ssl/corp-ca.crt',
},
});Gets the hostname of the current machine.
function hostname(): string;Returns: The hostname of the machine
Runtime Implementation:
-
Deno: Uses
Deno.hostname() -
Bun: Uses Node.js-compatible
os.hostname() -
Node.js: Uses native
os.hostname() -
Unknown runtime: Returns
'localhost'as fallback
Example - Basic usage:
import { hostname } from '@tundralibs/compat/net';
const host = hostname();
console.log(`Machine hostname: ${host}`);
// Output: Machine hostname: my-computerExample - Server logging:
import { hostname, listen } from '@tundralibs/compat/net';
async function startServer(port: number) {
const host = hostname();
const listener = await listen({ port });
console.log(`Server running on ${host}:${port}`);
return listener;
}
const listener = await startServer(8080);import { listen } from '@tundralibs/compat/net';
async function findAvailablePort(
startPort: number,
endPort: number,
): Promise<number | null> {
for (let port = startPort; port <= endPort; port++) {
try {
const listener = await listen({ port });
listener.close();
return port;
} catch {
// Port is in use, try next
}
}
return null;
}
const port = await findAvailablePort(8000, 8100);
if (port) {
console.log(`Found available port: ${port}`);
} else {
console.log('No available ports in range');
}import { connect, hostname, listen } from '@tundralibs/compat/net';
async function startEchoServer(port: number) {
const listener = await listen({ port });
const host = hostname();
console.log(`Echo server listening on ${host}:${port}`);
return listener;
}
// Start server
const server = await startEchoServer(8080);
// Later, close the server
server.close();import { connect } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
async function checkTcpHealth(
hostname: string,
port: number,
timeout = 5000,
): Promise<boolean> {
try {
const conn = await connect({ hostname, port, timeout });
conn.close();
return true;
} catch (err) {
if (err instanceof ConnectionTimeoutError) {
console.log(`Connection to ${hostname}:${port} timed out`);
}
return false;
}
}
const isHealthy = await checkTcpHealth('example.com', 80);
console.log(`Server is ${isHealthy ? 'healthy' : 'down'}`);import { connect } from '@tundralibs/compat/net';
async function httpGet(hostname: string, path: string): Promise<string> {
const conn = await connect({ hostname, port: 80 });
// Send HTTP request
const request =
`GET ${path} HTTP/1.1\r\nHost: ${hostname}\r\nConnection: close\r\n\r\n`;
await conn.write(request);
// Read response
const chunks: Uint8Array[] = [];
while (true) {
const chunk = await conn.read();
if (chunk === null) break;
chunks.push(chunk);
}
conn.close();
// Combine chunks
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const combined = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.length;
}
const decoder = new TextDecoder();
return decoder.decode(combined);
}
const response = await httpGet('example.com', '/');
console.log(response);import { connect } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
async function isPortOpen(
hostname: string,
port: number,
timeout = 1000,
): Promise<boolean> {
try {
const conn = await connect({ hostname, port, timeout });
conn.close();
return true;
} catch {
return false;
}
}
async function scanPorts(hostname: string, ports: number[]): Promise<number[]> {
const results = await Promise.all(
ports.map(async (port) => ({
port,
open: await isPortOpen(hostname, port),
})),
);
return results.filter((r) => r.open).map((r) => r.port);
}
const openPorts = await scanPorts('localhost', [80, 443, 3000, 8080]);
console.log('Open ports:', openPorts);import { connect, type Connection } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
async function connectWithRetry(
hostname: string,
port: number,
maxRetries = 3,
timeout = 5000,
): Promise<Connection> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Connection attempt ${attempt}/${maxRetries}`);
const conn = await connect({ hostname, port, timeout });
console.log('Connected successfully');
return conn;
} catch (err) {
if (err instanceof ConnectionTimeoutError) {
console.log(`Attempt ${attempt} timed out`);
} else {
console.log(`Attempt ${attempt} failed:`, (err as Error).message);
}
// Wait before retrying
await new Promise((r) => setTimeout(r, 1000));
}
}
throw new Error(`Failed to connect after ${maxRetries} attempts`);
}
const conn = await connectWithRetry('example.com', 80);
conn.close();import { listen, type Listener } from '@tundralibs/compat/net';
class Server {
private controller: AbortController;
private listener: Listener;
private constructor(listener: Listener, controller: AbortController) {
this.listener = listener;
this.controller = controller;
}
static async create(port: number) {
const controller = new AbortController();
const listener = await listen({
port,
signal: controller.signal,
});
console.log(`Server started on port ${port}`);
return new Server(listener, controller);
}
async run() {
try {
while (true) {
const conn = await this.listener.accept();
// Handle each connection without blocking the accept loop
void this.handleConnection(conn);
}
} catch {
// Listener was closed (signal aborted) — exit the loop cleanly
console.log('Server stopped');
}
}
private async handleConnection(
conn: Awaited<ReturnType<Listener['accept']>>,
) {
try {
const data = await conn.read();
if (data) await conn.write(data);
} finally {
conn.close();
}
}
shutdown() {
console.log('Shutting down server...');
this.controller.abort(); // Automatically closes listener
}
}
const server = await Server.create(8080);
// Run server in background
server.run();
// Shutdown after 10 seconds
setTimeout(() => server.shutdown(), 10000);import { connect } from '@tundralibs/compat/net';
import { ConnectionTimeoutError } from '@tundralibs/compat';
try {
const conn = await connect({
hostname: 'slow-server.example.com',
port: 80,
timeout: 3000, // 3 second timeout
});
conn.close();
} catch (error) {
if (error instanceof ConnectionTimeoutError) {
console.error('Connection timed out after', error.timeoutMs, 'ms');
console.error('Target:', error.hostname, error.port);
} else if (error instanceof Error) {
console.error('Connection failed:', error.message);
}
}import { hostname, listen } from '@tundralibs/compat/net';
import { RUNTIME } from '@tundralibs/compat/runtime';
async function displayServerInfo(port: number) {
const host = hostname();
const listener = await listen({ port });
console.log('Server Information:');
console.log(` Runtime: ${RUNTIME}`);
console.log(` Hostname: ${host}`);
console.log(` Port: ${port}`);
console.log(` Listening on: http://${host}:${port}`);
listener.close();
}
await displayServerInfo(8080);The net module throws standard JavaScript errors and runtime-specific errors:
import { listen } from '@tundralibs/compat/net';
try {
const listener = await listen({ port: 8080 });
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('address already in use')) {
console.error('Port 8080 is already in use');
} else {
console.error('Failed to create listener:', error.message);
}
}
}import { connect } from '@tundralibs/compat/net';
try {
const conn = await connect({ hostname: 'invalid-host', port: 80 });
} catch (error) {
if (error instanceof Error) {
console.error('Connection failed:', error.message);
// Handle connection failure (host not found, timeout, etc.)
}
}import { listen } from '@tundralibs/compat/net';
import { UnsupportedRuntimeError } from '@tundralibs/compat';
try {
const listener = await listen({ port: 8080 });
} catch (error) {
if (error instanceof UnsupportedRuntimeError) {
console.error('This runtime is not supported');
}
}import { connect, listen } from '@tundralibs/compat/net';
// Listeners and connections are safe to close multiple times
const listener = await listen({ port: 8080 });
listener.close();
listener.close(); // Safe - no error thrown
const conn = await connect({ port: 8080 });
conn.close();
conn.close(); // Safe - no error thrown- Compat-Common - TLSOptions type and TLS error classes
- Compat-Fetch - HTTP client with TLS and Unix socket support
- Compat-Runtime - Runtime detection utilities