npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@perryts/dgram

v0.1.6

Published

UDP datagram socket bindings (Node `dgram` module) for the Perry TypeScript-to-native compiler.

Readme

dgram

Native bindings for Node's dgram module — UDP datagram sockets — for the Perry TypeScript-to-native compiler.

Closes PerryTS/perry#492.

What this is

A Perry "native library" package: a Rust crate (built on top of tokio::net::UdpSocket) exporting extern "C" symbols that the Perry compiler links into your TypeScript program. From your TypeScript code you import * as dgram from "dgram" like any npm package; under the hood every method call resolves to a direct call into the bundled staticlib — no Node addon, no IPC, no JSON marshalling.

This package contains:

  • src/lib.rs — the Rust crate that wraps tokio::net::UdpSocket and exposes js_dgram_* extern "C" symbols
  • src/index.d.ts — the TypeScript surface (dgram module declaration) Perry resolves at compile time
  • Cargo.toml — staticlib build config consumed by the Perry linker
  • package.json — includes the perry.nativeLibrary manifest block

Install

bun add @perryts/dgram
# or
npm install @perryts/dgram

The package's package.json declares a perry.nativeLibrary block (see the manifest spec) which Perry's compiler reads at link time to discover the staticlib + extern "C" symbols. No post-install build step — Perry compiles the Rust crate as part of your project's build.

Quick start

A UDP echo server. The server binds, awaits each datagram via recv, and sends the same bytes back to the originating peer. The client binds an ephemeral port, sends a single message, awaits the reply, and exits.

Server

import * as dgram from "dgram";

const sock = dgram.createSocket("udp4");
await dgram.bind(sock, 8000);
console.log("listening on", dgram.address(sock));

while (true) {
  const { msg, rinfo } = await dgram.recv(sock, 65_536);
  console.log("recv from", rinfo.address, rinfo.port, "->", new TextDecoder().decode(msg));
  await dgram.sendBuffer(sock, msg, rinfo.port, rinfo.address);
}

Client

import * as dgram from "dgram";

const sock = dgram.createSocket("udp4");
await dgram.bind(sock, 0); // ephemeral port
await dgram.send(sock, "hello, server!", 8000, "127.0.0.1");

const { msg } = await dgram.recv(sock, 65_536);
console.log("server replied:", new TextDecoder().decode(msg));

await dgram.close(sock);

Why a Promise-based recv instead of socket.on('message', cb)

Node's idiomatic dgram API is event-driven (socket.on('message', handler)). External Perry bindings can't register their own event-pump with perry-stdlib's event-loop dispatcher today, so v0.1 exposes recv(socket, maxBytes): Promise<{ msg, rinfo }> instead. Most modern code is happier with the await-loop shape anyway, and an event-emitter wrapper is a few lines of TypeScript:

import * as dgram from "dgram";
import { EventEmitter } from "events";

export function asEventSocket(s: dgram.SocketHandle, maxBytes = 65_536) {
  const emitter = new EventEmitter();
  let running = true;
  (async () => {
    while (running) {
      try {
        const { msg, rinfo } = await dgram.recv(s, maxBytes);
        emitter.emit("message", msg, rinfo);
      } catch (e) {
        emitter.emit("error", e);
        return;
      }
    }
  })();
  return { emitter, stop: () => { running = false; } };
}

A native on('message', cb) surface is a v0.2 followup once perry-ffi grows per-wrapper pump registration.

API reference

createSocket(type)

type SocketType = "udp4" | "udp6";

function createSocket(type: SocketType): SocketHandle;

Allocate an unbound UDP socket. Synchronous. Returns an opaque branded handle. No I/O happens until bind is called.

bind(socket, port, address?)

function bind(socket: SocketHandle, port: number, address?: string): Promise<void>;

Bind the socket. Pass 0 as the port to let the kernel assign a free port (read it back via address). When address is omitted the socket binds to 0.0.0.0 (udp4) or :: (udp6). Rejects if the socket is already bound or the bind syscall fails (port in use, permission denied, etc).

send(socket, msg, port, address) and sendBuffer(socket, buffer, port, address)

function send(s: SocketHandle, msg: string, port: number, address: string): Promise<number>;
function sendBuffer(s: SocketHandle, buf: Uint8Array | Buffer, port: number, address: string): Promise<number>;

Send a UTF-8 string or arbitrary bytes to a remote endpoint. Resolves with the byte count actually written (send_to's return). For datagrams larger than the path MTU the kernel will either fragment (IPv4) or return EMSGSIZE — at the JS layer that surfaces as a rejection.

recv(socket, maxBytes)

interface RemoteInfo {
  address: string;
  family: "IPv4" | "IPv6";
  port: number;
  size: number;
}

interface IncomingMessage {
  msg: Uint8Array;
  rinfo: RemoteInfo;
}

function recv(s: SocketHandle, maxBytes: number): Promise<IncomingMessage>;

Await the next incoming datagram. maxBytes caps the read buffer (clamped to [1, 65_536] internally). Concurrent recv calls on the same socket are serialized — packets are delivered in receive order to whichever recv is first in line.

If you're not actively awaiting recv, packets the kernel receives are buffered up to its receive queue and then dropped silently. That's standard UDP semantics.

close(socket)

function close(s: SocketHandle): Promise<void>;

Drop the socket. The underlying fd closes when no concurrent operation holds a reference; pending recv calls reject with a closed-socket error. Idempotent.

address(socket)

interface AddressInfo {
  address: string;
  family: "IPv4" | "IPv6";
  port: number;
}

function address(s: SocketHandle): AddressInfo | null;

Synchronous read of the local bound address. Returns null if the socket isn't bound.

setBroadcast(socket, flag)

function setBroadcast(s: SocketHandle, flag: boolean): boolean;

Toggle SO_BROADCAST on the underlying fd. Required before sending to broadcast addresses (255.255.255.255, subnet broadcasts). Returns true on success.

addMembership(socket, multicastAddress, interfaceAddress?) and dropMembership(...)

function addMembership(s: SocketHandle, multi: string, iface?: string): boolean;
function dropMembership(s: SocketHandle, multi: string, iface?: string): boolean;

Join / leave a multicast group. For IPv4, interfaceAddress is a textual IPv4 (e.g. "192.168.1.10") — empty string lets the kernel pick. For IPv6, pass a numeric interface index as a string ("0" = let the kernel pick); textual interface name resolution is a v0.2 followup.

Types

type SocketHandle = number & { readonly __dgramSocket: unique symbol };

SocketHandle is an opaque branded number — never inspect it or do arithmetic on it.

Error handling

Every async function rejects with an Error whose message is prefixed by the operation, e.g. dgram bind: Address already in use (os error 48). Common rejection reasons:

  • Invalid handle (dgram <op>: invalid socket handle) — you passed a handle that was never returned by this library, or one that was already consumed by close.
  • Already bound (dgram bind: socket already bound) — bind was called twice on the same handle.
  • Not bound (dgram <send|recv>: socket not bound) — you have to bind before sending or receiving.
  • Bind failed — kernel-level errors flow through verbatim.

Status & roadmap

What's there:

  • createSocket / bind / send / sendBuffer / recv / close / address
  • setBroadcast for broadcast clients
  • addMembership / dropMembership for multicast group receivers

Known gaps, tracked in PerryTS/perry:

  • Event-driven socket.on('message', cb) surface — v0.1 is await recv() only; needs per-wrapper pump registration in perry-ffi
  • setMulticastTTL / setMulticastInterface / setTTL socket options
  • connect(host, port) for connected-UDP semantics
  • Textual interface-name → IPv6 interface-index resolution for addMembership

Versioning

Pre-1.0. The perry.nativeLibrary.abiVersion (currently 0.5) is a hard pin against Perry's perry-ffi ABI — bump it in lockstep with the Perry release that the bindings target.

License

MIT — see LICENSE.