DatagramSocket
Pull-based UDP sockets with scoped native handles.
Acquiring a reader opens and binds a socket owned by its scope. Only one
reader is open at a time; another acquisition waits interruptibly for it to
close. Use Effect.retry around a scoped consume loop to open a new socket
on each retry.
Writes wait while no reader is open, so a send-only client must still acquire a reader.
Example Portable request/reply client
import { Effect } from "effect"import { NetAddress } from "effect/net"import { DatagramSocket } from "effect/socket"
const server = NetAddress.inetAddressFromIpStringUnsafe("127.0.0.1", 5353)
export const request = (payload: string) => Effect.gen(function*() { const socket = yield* DatagramSocket.DatagramSocket const reader = yield* socket.reader const writer = yield* socket.writer yield* writer.write({ payload, address: server }) const [response] = yield* reader.pull return response.payload }).pipe(Effect.scoped)Constructors
Creates a DatagramSocket from its reader and writer effects.
Signature
declare function make(options: { readonly reader: Effect<Reader, DatagramSocketError, Scope>; readonly writer: Effect<Writer, never, Scope>;}): DatagramSocketBuilds a DatagramSocket over a backing socket, opening a new socket for
each reader acquisition.
Details
open receives the event callbacks before the handle exists, so packets can
be queued while it opens. Core owns the receive queue and its overflow
strategy, reader exclusivity, sticky errors, lazy address parsing, the
writer latch and destination formatting. The handle is closed when the
reader's scope closes. If acquisition is interrupted while open is still
running, the interruption returns at once, open is left to finish, and
the handle it produces is closed. The next reader waits until that open has
settled, so two native sockets never exist at once. An adopted acquire
must therefore finish in bounded time.
open runs in the reader's scope, with the services of the fiber that
built the socket, so an adopted acquire can use them and register
finalizers that run when the reader closes. Where the fiber acquiring the
reader has a service with the same tag, its own takes precedence.
Concurrent pulls are served in the order they started waiting.
onError receives errors with no write left to fail. It runs
synchronously, and anything it throws is ignored.
Invalid receiveBuffer.capacity values cause a defect; use a positive
safe integer.
Signature
declare function makeFromBackingSocket<R = never>(open: (events: BackingEvents) => Effect<BackingSocket, DatagramSocketError, R>, options?: { readonly onError?: (error: DatagramSocketError) => void; readonly receiveBuffer?: ReceiveBufferOptions;}): Effect<DatagramSocket, never, Exclude<R, Scope>>Errors
The native socket closed underneath the reader, or the reader's scope closed.
Signature
declare class DatagramSocketClosedError extends { readonly _tag: "DatagramSocketClosedError";} & YieldableError<this> { constructor(...args: [props?: { readonly _tag?: "DatagramSocketClosedError"; }, options?: MakeOptions]);}The error raised by DatagramSocket operations, wrapping a specific
reason. Its cause and message come from the reason.
Signature
declare class DatagramSocketError extends { readonly _tag: "DatagramSocketError"; readonly reason: DatagramSocketOpenError | DatagramSocketReadError | DatagramSocketWriteError | DatagramSocketClosedError | DatagramSocketUnsupportedError;} & YieldableError<this> { constructor(props: { readonly reason: DatagramSocketOpenError | DatagramSocketReadError | DatagramSocketWriteError | DatagramSocketClosedError | DatagramSocketUnsupportedError; }); readonly "~effect/socket/DatagramSocket/DatagramSocketError": "~effect/socket/DatagramSocket/DatagramSocketError"; message: string;}Schema for the union of DatagramSocketError reasons.
Signature
declare const DatagramSocketErrorReason: Union<readonly [typeof DatagramSocketOpenError, typeof DatagramSocketReadError, typeof DatagramSocketWriteError, typeof DatagramSocketClosedError, typeof DatagramSocketUnsupportedError]>The union of DatagramSocketError reasons.
Signature
type DatagramSocketErrorReason = typeof DatagramSocketErrorReason.TypeRuntime type identifier attached to DatagramSocketError values.
Signature
declare const DatagramSocketErrorTypeId: "~effect/socket/DatagramSocket/DatagramSocketError"Opening or binding the native socket, applying its options, or resolving a name failed.
Signature
declare class DatagramSocketOpenError extends { readonly _tag: "DatagramSocketOpenError"; readonly cause: unknown; readonly kind: "Unknown" | "PermissionDenied" | "AddressInUse" | "AddressNotAvailable";} & YieldableError<this> { constructor(...args: [props: { readonly _tag?: "DatagramSocketOpenError"; readonly cause: unknown; readonly kind: "Unknown" | "PermissionDenied" | "AddressInUse" | "AddressNotAvailable"; }, options?: MakeOptions]);}A receive failed, or an ICMP report arrived with no write left to fail.
Signature
declare class DatagramSocketReadError extends { readonly _tag: "DatagramSocketReadError"; readonly cause: unknown; readonly kind: "Unknown" | "PermissionDenied" | "MessageTooLarge" | "Unreachable" | "ConnectionRefused";} & YieldableError<this> { constructor(...args: [props: { readonly _tag?: "DatagramSocketReadError"; readonly cause: unknown; readonly kind: "Unknown" | "PermissionDenied" | "MessageTooLarge" | "Unreachable" | "ConnectionRefused"; }, options?: MakeOptions]);}The runtime lacks a capability, such as source-specific multicast on Deno.
Signature
declare class DatagramSocketUnsupportedError extends { readonly _tag: "DatagramSocketUnsupportedError"; readonly capability: string; readonly runtime: string;} & YieldableError<this> { constructor(...args: [props: { readonly _tag?: "DatagramSocketUnsupportedError"; readonly capability: string; readonly runtime: string; }, options?: MakeOptions]); message: string;}A send failed. address is the destination when it is known.
Signature
declare class DatagramSocketWriteError extends { readonly _tag: "DatagramSocketWriteError"; readonly address?: InetAddress; readonly cause: unknown; readonly kind: "Unknown" | "PermissionDenied" | "MessageTooLarge" | "Unreachable" | "ConnectionRefused";} & YieldableError<this> { constructor(...args: [props: { readonly _tag?: "DatagramSocketWriteError"; readonly address?: InetAddress; readonly cause: unknown; readonly kind: "Unknown" | "PermissionDenied" | "MessageTooLarge" | "Unreachable" | "ConnectionRefused"; }, options?: MakeOptions]);}IoErrorKind
typeThe normalized kind of a send or receive failure.
Signature
type IoErrorKind = "MessageTooLarge" | "Unreachable" | "ConnectionRefused" | "PermissionDenied" | "Unknown"Guards
Returns true when a value is a DatagramSocketError.
Signature
declare function isDatagramSocketError(u: unknown): u is DatagramSocketErrorModels
BackingAddress
interfaceA native host and port, parsed on demand.
Signature
interface BackingAddress { readonly host: string; readonly port: number;}BackingEvents
interfaceCallbacks installed before opening the backing socket; packets may arrive synchronously.
Details
onPacket queues a received packet. onReadError is a terminal receive
error and onClose reports the native socket closing underneath the
reader; both are sticky. onError forwards errors with no write left to
fail, such as ICMP reports, to the user's onError option.
Each callback can be passed on its own, for example as a runtime's event listener.
Signature
interface BackingEvents { readonly onClose: () => void; readonly onError: (error: DatagramSocketError) => void; readonly onPacket: (payload: Uint8Array, host: string, port: number) => void; readonly onReadError: (error: DatagramSocketError) => void;}BackingSocket
interfaceNative transport contract used by platform adapters.
Details
address is the raw bound address, read once at open. scopeIds maps IPv6
zone names to scope IDs, for parsing named zones and formatting
destinations. peer is the default destination, and connected marks a
natively connected socket, whose sends receive no destination.
send and sendMany hand datagrams to the runtime and call done exactly
once, synchronously or later, when the runtime reports the result. They must
not throw. sendMany passes the index of the failing datagram to done
when the runtime knows it, so core can attach its address.
Adapters normalize native errors before passing them to core.
Read destination host and port during the call; do not retain the record.
close must not throw.
Signature
interface BackingSocket { readonly address: BackingAddress; readonly close: () => void; readonly connected?: boolean; readonly joinMulticast: <A extends IpAddress>(options: { readonly group: MulticastAddress<A>; readonly interface?: MulticastInterface<A>; readonly source?: A; }) => Effect<() => Effect<void, never>, DatagramSocketError>; readonly peer?: BackingAddress; readonly scopeIds?: ReadonlyMap<string, number>; readonly send: (payload: Uint8Array, destination: BackingAddress | undefined, done: (error?: DatagramSocketError) => void) => void; readonly sendMany: (payloads: readonly Array<Uint8Array<ArrayBufferLike>>, destinations: readonly Array<BackingAddress | undefined>, done: (error?: DatagramSocketError, index?: number) => void) => void;}A received packet.
Details
The payload is safe to retain. The sender's address is parsed on first
read and cached.
Reply with writer.write({ payload, address: received }) to reuse the raw
sender address without parsing. This requires a datagram from a platform
adapter or makeFromBackingSocket. For hand-built datagrams or custom
readers, use { payload, address: received.address }.
Signature
interface Datagram { readonly address: InetAddress; readonly payload: Uint8Array;}DatagramSocket
interfaceA UDP socket with a scoped, exclusive reader. Acquiring reader opens and
binds a native socket; subsequent acquisitions wait for its scope to close.
Acquiring writer cannot fail, but writes wait for an open reader.
Signature
interface DatagramSocket { readonly "~effect/socket/DatagramSocket": "~effect/socket/DatagramSocket"; readonly reader: Effect<Reader, DatagramSocketError, Scope>; readonly writer: Effect<Writer, never, Scope>;}OutgoingDatagram
interfaceA packet to send.
Details
String payloads are encoded as UTF-8. address can be omitted when the
socket has a peer or is connected. On a connected socket an explicit
InetAddress fails the write, while a received Datagram is allowed and
goes out on the connected path.
Pass a received Datagram as the destination to reply, or as the whole
argument to echo it. Both reuse the raw sender address and require a
datagram from a platform adapter or makeFromBackingSocket. Hand-built
datagrams and custom readers must pass an explicit InetAddress instead.
Signature
interface OutgoingDatagram { readonly address?: InetAddress | Datagram; readonly payload: string | Uint8Array<ArrayBufferLike>;}The receive side of an open native socket.
Details
pull returns everything queued since the last pull, or waits for the next
packet. After a terminal error, pull first returns the queued packets and
then fails with the same error until the reader's scope closes. Closing the
scope discards the queue and fails every waiting pull with
DatagramSocketClosedError.
Concurrent pulls wait in FIFO order. Each incoming packet goes to the oldest waiter; a terminal error fails all waiters.
address is the bound local address, parsed on first read and cached.
dropped counts receive-buffer overflow since this reader opened, not
kernel or network loss or packets discarded on scope close.
joinMulticast joins a group until its scope closes; leave failures are
ignored. Its interface selects where packets arrive (ingress), not the
platform's multicast.interface for sending (egress).
Signature
interface Reader { readonly address: InetAddress; readonly dropped: () => number; readonly joinMulticast: <A extends IpAddress>(options: { readonly group: MulticastAddress<A>; readonly interface?: MulticastInterface<A>; readonly source?: A; }) => Effect<void, DatagramSocketError, Scope>; readonly pull: Effect<readonly [Datagram, Datagram], DatagramSocketError>;}ReceiveBufferOptions
interfaceReceive queue options.
Details
The queue is bounded in packets, with no byte cap. capacity defaults to
1024 and must be a positive safe integer. The worst-case
memory is capacity ร 64 KiB, about 64 MiB at the default.
When the queue is full, "dropping" (the default) discards the incoming
packet and "sliding" discards the oldest queued one. Both count the
packet in Reader.dropped.
Signature
interface ReceiveBufferOptions { readonly capacity?: number; readonly strategy?: "sliding" | "dropping";}The send side of a DatagramSocket. Writes wait until a reader is open, so
a send-only client must still acquire a reader.
Details
A write completes when the runtime reports the send's result, and send
errors fail it with a DatagramSocketWriteError carrying the destination
address. After a terminal reader error, writes fail with that error until
the reader's scope closes, and then wait for the next reader.
writeAll fails with the first error, without resending. Partial delivery is
unspecified. The error has no packet index, and its address is absent on Bun.
There is no built-in write timeout; use Effect.timeout.
Signature
interface Writer { readonly write: (datagram: OutgoingDatagram) => Effect<void, DatagramSocketError>; readonly writeAll: (datagrams: readonly [OutgoingDatagram, OutgoingDatagram]) => Effect<void, DatagramSocketError>;}Services
Service tag for UDP socket transports.
Signature
declare const DatagramSocket: Context.Service<DatagramSocket, DatagramSocket>