Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

DenoDatagramSocket

Deno UDP adapter for effect/socket/DatagramSocket, built on the native Deno.listenDatagram.

make opens a new Deno.DatagramConn for each reader acquisition, fromDatagramConn adopts a connection the caller opens, and layer provides the DatagramSocket service.

Deno.listenDatagram is unstable. Run with --unstable-net, or add "net" to the "unstable" list in deno.json. Without it, acquiring a reader fails with a DatagramSocketUnsupportedError, and without --allow-net it fails with a DatagramSocketOpenError of kind "PermissionDenied".

Compared with the Node and Bun adapters, Deno has no connect, no source-specific multicast, and broadcast is always on. Received packets are read into one reused 64 KiB buffer and copied out, so payloads are safe to retain. On Linux a datagram larger than that buffer is silently truncated.

The reader owns the native socket, so a send-only client must still acquire a reader, or its writes wait forever. Every write waits for Deno's send promise, so send errors fail the write that caused them.

writeAll sends sequentially and stops at the first failure. Earlier sends are not resent; the error carries the failed datagram's address. Send order does not guarantee arrival order.

On Windows, ICMP connection resets or refusals reported by receive are passed to onError and reading continues.

Example Echo server

import { DenoDatagramSocket } from "@effect/platform-deno"
import { Effect } from "effect"
const echo = Effect.gen(function*() {
const socket = yield* DenoDatagramSocket.make({ bind: { port: 9000 } })
const reader = yield* socket.reader
const writer = yield* socket.writer
while (true) {
for (const received of yield* reader.pull) {
yield* writer.write({ payload: received.payload, address: received })
}
}
}).pipe(Effect.scoped)

Example Send-only client

import { DenoDatagramSocket } from "@effect/platform-deno"
import { Effect } from "effect"
const send = (payload: string) =>
Effect.gen(function*() {
const socket = yield* DenoDatagramSocket.make({
peer: { address: "localhost", port: 9000 }
})
// Required even for send-only clients.
yield* socket.reader
const writer = yield* socket.writer
yield* writer.write({ payload })
}).pipe(Effect.scoped)

Example Multicast receiver and sender

import { DenoDatagramSocket } from "@effect/platform-deno"
import { Effect } from "effect"
import { NetAddress } from "effect/net"
const group = NetAddress.ipFromStringUnsafe("239.255.0.1")
const receive = Effect.gen(function*() {
if (!NetAddress.isMulticast(group) || !NetAddress.isIpv4Address(group)) return
const socket = yield* DenoDatagramSocket.make({
bind: { address: "0.0.0.0", port: 5000 },
reuseAddress: true
})
const reader = yield* socket.reader
yield* reader.joinMulticast({ group, interface: NetAddress.ipv4Loopback })
return yield* reader.pull
}).pipe(Effect.scoped)
const announce = Effect.gen(function*() {
const socket = yield* DenoDatagramSocket.make({
// Deno has no egress `multicast.interface` option, so the bind address
// picks the interface the group's datagrams leave through
bind: { address: "127.0.0.1" },
peer: { address: group, port: 5000 },
multicast: { loopback: true }
})
yield* socket.reader
const writer = yield* socket.writer
yield* writer.write({ payload: "hello" })
}).pipe(Effect.scoped)
6 exports Since v4.0.0 Source

Constructors

Adopts a Deno.DatagramConn.

Details

acquire runs once per reader acquisition, inside the reader's scope, and the adapter closes the connection when that scope ends. The adapter starts reading as soon as acquire completes, so nothing else should read from the connection.

Signature

declare function fromDatagramConn<R>(acquire: Effect<DatagramConn, DatagramSocketError, R>, options: AdoptOptions): Effect<DatagramSocket, never, Exclude<R, Scope>>
Since v4.0.0

Creates a DatagramSocket that opens and binds a new Deno.DatagramConn for each reader acquisition.

Details

Creating the socket never fails. Checking for Deno.listenDatagram, binding and resolving names happen when a reader is acquired, and fail that acquisition with a DatagramSocketError. Hostnames in bind and peer are resolved once per acquisition, so no send waits for DNS.

Invalid receiveBuffer.capacity values cause a defect; use a positive safe integer.

Signature

declare function make(options: Options): Effect<DatagramSocket>

Layers

Since v4.0.0

Provides a DatagramSocket built with make.

Signature

declare function layer(options: Options): Layer<DatagramSocket>

Models

Endpoint

interface
Since v4.0.0

An open-time endpoint. Hostnames resolve once per reader acquisition via node:dns.lookup. An InetAddress can be passed directly.

Signature

interface Endpoint {
readonly address: string | IpAddress;
readonly port: number;
}

Options

Since v4.0.0

Options for fromDatagramConn. The caller configures the adopted connection, so only the options that live in JavaScript apply.

Signature

type AdoptOptions = Pick<Options, "peer" | "receiveBuffer" | "onError">

Options

interface
Since v4.0.0

Options for make and layer.

Details

bind defaults to 0.0.0.0 (or :: for family: "ipv6") and an ephemeral port. peer is a default destination and doesn't filter senders; Deno has no connect.

Family selection, in order: explicit family, a bind IP literal, the peer address, a bind hostname, then "ipv4". Hostname lookups use the family selected so far, or prefer IPv4 if none is set.

reuseAddress means SO_REUSEADDR on Linux and SO_REUSEPORT on BSD and macOS. multicast.loopback loops sent multicast datagrams back to the local host. Deno has no egress multicast interface setting; the interface of Reader.joinMulticast is the ingress interface, a different socket option.

Signature

interface Options {
readonly bind?: {
readonly address?: string | IpAddress;
readonly port?: number;
};
readonly family?: "ipv4" | "ipv6";
readonly multicast?: {
readonly loopback?: boolean;
};
readonly onError?: (error: DatagramSocketError) => void;
readonly peer?: Endpoint;
readonly receiveBuffer?: any;
readonly reuseAddress?: boolean;
}