/** * Bun UDP adapter for `effect/socket/DatagramSocket`, built on the native * `Bun.udpSocket`. Requires Bun 1.4 or later: earlier versions throw `EAGAIN` * from `send` instead of returning `false`, and never fire `drain`. * * `make` opens a new Bun socket for each reader acquisition, `fromUdpSocket` * adopts a socket the caller creates, and `layer` provides the * `DatagramSocket` service. * * The reader owns the native socket, so a send-only client must still acquire * a reader, or its writes wait forever. Bun sends synchronously: a send error * fails the write that caused it, and when the kernel buffer is full the write * waits for `drain` and sends what is left. * * `writeAll` sends the batch with one `sendMany` call and fails with its first * error. Which datagrams went out is unspecified, nothing is resent, and the * error has no `address`, because Bun reports one error for the whole call. * * ICMP reports, such as a refused port, arrive after the write has completed, * so they go to the `onError` option as a `DatagramSocketReadError` with no * address. * * On macOS and Windows a poll error closes the socket without any event. The * adapter notices on the next write, which fails with * `DatagramSocketClosedError`, as do the reader and any write waiting for * `drain`. Until then those waiting writes and `pull` keep waiting. The same * applies when the caller closes a socket adopted with `fromUdpSocket`. * * **Example** (Echo server) * * ```ts * import { BunDatagramSocket } from "@effect/platform-bun" * import { Effect } from "effect" * * const echo = Effect.gen(function*() { * const socket = yield* BunDatagramSocket.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 with a connected socket) * * ```ts * import { BunDatagramSocket } from "@effect/platform-bun" * import { Effect } from "effect" * * const send = (payload: string) => * Effect.gen(function*() { * const socket = yield* BunDatagramSocket.make({ * connect: { 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** (Adopting a socket) * * ```ts * import { BunDatagramSocket } from "@effect/platform-bun" * import { Effect } from "effect" * * const receive = Effect.gen(function*() { * // `fromUdpSocket` replaces the socket's handlers with its own * const socket = yield* BunDatagramSocket.fromUdpSocket( * Effect.promise(() => Bun.udpSocket({ hostname: "127.0.0.1", port: 9000 })) * ) * const reader = yield* socket.reader * return yield* reader.pull * }).pipe(Effect.scoped) * ``` * * @stability experimental * @since 4.0.0 */ import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as NetAddress from "effect/net/NetAddress"; import type * as Scope from "effect/Scope"; import * as DatagramSocket from "effect/socket/DatagramSocket"; /** * An open-time endpoint. Hostnames resolve once per reader acquisition via * `node:dns.lookup`, avoiding Bun's synchronous `getaddrinfo`. * An `InetAddress` can be passed directly. * * @stability experimental * @category models * @since 4.0.0 */ export interface Endpoint { readonly address: string | NetAddress.IpAddress; readonly port: number; } /** * The local address and port to bind. Defaults to `0.0.0.0` (or `::` for * `family: "ipv6"`) and an ephemeral port. * * @stability experimental * @category models * @since 4.0.0 */ export interface BindOptions { readonly address?: string | NetAddress.IpAddress | undefined; readonly port?: number | undefined; } /** * Options for `fromUdpSocket`. The caller configures the adopted socket, so * only the options that live in JavaScript apply. * * @stability experimental * @category models * @since 4.0.0 */ export interface AdoptOptions { readonly peer?: Endpoint | undefined; readonly receiveBuffer?: DatagramSocket.ReceiveBufferOptions | undefined; readonly onError?: ((error: DatagramSocket.DatagramSocketError) => void) | undefined; } /** * Options for `make` and `layer`. * * **Details** * * `peer` is a default destination and doesn't filter senders. `connect` * connects the socket when Bun creates it, so the kernel filters senders. The * two can't be combined. * * Family selection, in order: explicit `family`, a `bind` IP literal, the * `peer` or `connect` 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.interface` is the egress interface for datagrams sent to a group, * which is a different socket option from the ingress `interface` of * `Reader.joinMulticast`. * * @stability experimental * @category models * @since 4.0.0 */ export type Options = { readonly bind?: BindOptions | undefined; readonly family?: "ipv4" | "ipv6" | undefined; readonly reuseAddress?: boolean | undefined; readonly reusePort?: boolean | undefined; readonly ipv6Only?: boolean | undefined; readonly broadcast?: boolean | undefined; readonly ttl?: number | undefined; readonly multicast?: { readonly interface?: NetAddress.MulticastInterface | undefined; readonly loopback?: boolean | undefined; readonly ttl?: number | undefined; } | undefined; readonly receiveBuffer?: DatagramSocket.ReceiveBufferOptions | undefined; readonly onError?: ((error: DatagramSocket.DatagramSocketError) => void) | undefined; } & ({ readonly peer?: Endpoint | undefined; readonly connect?: undefined; } | { readonly connect: Endpoint; readonly peer?: undefined; }); /** * A native Bun UDP socket, connected or not. * * @stability experimental * @category models * @since 4.0.0 */ export type UdpSocket = Bun.udp.Socket<"buffer"> | Bun.udp.ConnectedSocket<"buffer">; /** * Creates a `DatagramSocket` that opens and binds a new Bun UDP socket for * each reader acquisition. * * **Details** * * Creating the socket never fails. Binding, applying options and resolving * names happen on reader acquisition and fail it with a `DatagramSocketError`. * Hostnames in `bind`, `peer` and `connect` are resolved once per acquisition; * sends never wait for DNS. * * Invalid `receiveBuffer.capacity` values cause a defect; use a positive * safe integer. * * @stability experimental * @category constructors * @since 4.0.0 */ export declare const make: (options?: Options) => Effect.Effect; /** * Adopts a Bun UDP socket. * * **Details** * * `acquire` runs once per reader acquisition, inside the reader's scope, and * the adapter closes the socket when that scope ends. Whether the socket is * connected is checked once at open. * * The adapter installs its own `data`, `drain` and `error` handlers with * `socket.reload`, which replaces the ones the caller passed to * `Bun.udpSocket`. Packets that arrive before `acquire` completes still go to * the caller's handlers. * * @stability experimental * @category constructors * @since 4.0.0 */ export declare const fromUdpSocket: (acquire: Effect.Effect, options?: AdoptOptions) => Effect.Effect>; /** * Provides a `DatagramSocket` built with `make`. * * @stability experimental * @category layers * @since 4.0.0 */ export declare const layer: (options?: Options) => Layer.Layer; //# sourceMappingURL=BunDatagramSocket.d.ts.map