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

@primafuture/socket-fdx

v1.1.0

Published

Bounded Unix stream sockets with explicit file descriptor ownership.

Readme

@primafuture/socket-fdx

Unix stream sockets with explicit file descriptor ownership. Version: 1.1.0, Linux x64, glibc ≥ 2.28, Node ≥ 24.13.0. ISC license.

The library transports binary bytes and SCM_RIGHTS descriptors. It adds no framing, does not preserve write boundaries, and does not implement D-Bus. The separate USocket adapter supports the contract used by [email protected].

Quick start

import * as fdx from '@primafuture/socket-fdx';

const socket = await fdx.connect({ address: { path: '/your/private/directory/socket' } });
try {
  await socket.send(Buffer.from([1]), { fds: [callerOwnedFd], timeoutMs: 2000 });
  const reply = await socket.read({ bytes: 1, fdCount: 1 }, { timeoutMs: 2000 });
  if (reply) {
    using received = reply.fds[0]!;
    consumeBorrowedDescriptor(received.fd);
  }
} finally {
  socket.destroy();
}

Run the complete self-contained example after building: npx tsx examples/transfer.ts. It creates and cleans a private directory, transfers a real file, and demonstrates half-close. examples/ownership.ts demonstrates take().

Socket pairs and adoption

socketPair() synchronously returns a frozen tuple of two independently owned, connected AF_UNIX/SOCK_STREAM descriptors. Both raw endpoints are blocking and close-on-exec. Blocking mode is intentional: either endpoint can be duplicated into a child process or service manager as stdin, stdout, stderr, or another inherited descriptor without turning ordinary process writes into EAGAIN. The returned fd values are borrowed; the OwnedDescriptor wrappers remain responsible for closing them.

adoptSocket({ descriptor, limits? }) accepts any open, connected Unix stream OwnedDescriptor produced by the same canonical socket-fdx runtime, including a descriptor received through SCM_RIGHTS. It validates and duplicates the native socket, then initializes the existing bounded poll transport; libuv switches that endpoint's shared open-file-description to nonblocking mode during poll initialization. The wrapper is consumed only after native setup succeeds. On any validation or setup failure, the original status flags and wrapper ownership are preserved. Successful adoption sets descriptor.closed and transfers all further lifecycle responsibility to the returned Socket.

Nonblocking status belongs to an open-file-description, so successful adoption also affects any other duplicated references to that same endpoint. It does not affect the opposite endpoint returned by socketPair(). Do not pass structural lookalikes or raw numbers to adoptSocket; explicit package ownership is part of the safety contract.

const pair = fdx.socketPair();
const inheritedStdout = pair[0].fd;
startChildOrService({ stdoutFd: inheritedStdout }); // The receiver must duplicate it before this returns.
pair[0].close();

const output = fdx.adoptSocket({ descriptor: pair[1], limits: { receiveBytes: 1024 * 1024 } });
try {
  const chunk = await output.read({ bytes: expectedBytes, fdCount: 0 });
  consume(chunk?.data);
} finally {
  output.destroy();
}

Run npx tsx examples/socket-pair.ts for a complete child-stdout example.

Ownership and ordering

send(Uint8Array, { fds? }) snapshots bytes and duplicates every FD synchronously before returning its promise. Capacity reservation and acquisition happen before any bytes of that request are transmitted. Callers may immediately mutate their buffer or close their original FD. The library never closes caller-owned send descriptors. FD 0 and intentional repeated entries are valid. Duplicates share the underlying open file description, including file offsets: this is reference transfer, not a file-content copy.

At least one payload byte is required when sending FDs. The queue retains duplicates across EAGAIN, and releases them after the first positive sendmsg submits ancillary data. Later partial writes carry no FDs. Completion means local kernel acceptance, never peer acknowledgement. The library never automatically retries an entire failed request.

read({ bytes, fdCount }) consumes two ordered FIFO counts atomically. Incomplete reads retain both resources. A zero-FD read leaves descriptors library-owned for a later read; a zero-byte read can consume already received descriptors. The protocol determines counts and message boundaries. Reads execute in order; cancellation removes the incomplete request without discarding buffered input. Clean empty EOF returns null; EOF with insufficient buffered resources rejects with ERR_FDX_EOF and tears down the socket.

Every delivered OwnedDescriptor owns one kernel reference. fd only borrows its number. close() and [Symbol.dispose]() close at most once. take() returns the number and permanently invalidates wrapper ownership; the caller must close the result. Never independently close a borrowed fd. Unclaimed received FDs are closed on destruction and rejected receives. Garbage collection is only a safety net.

Limits and cancellation

| Per-socket cap | Default | | --- | ---: | | Retained send snapshot bytes | 8 MiB | | Buffered receive bytes | 8 MiB | | Pending send FDs | 256 | | Buffered receive FDs | 256 | | Accepted sends / pending reads | 1,024 each | | FDs in one send | 253 |

Set limits: { sendBytes, receiveBytes, sendFds, receiveFds, sendOperations, readOperations } on connect, listen, or adoptSocket. The active operation counts toward capacity. Admission rejects immediately instead of queuing unbounded capacity waiters. Incoming fragments are coalesced into 4 KiB blocks, with less than 8 KiB of unused boundary capacity per socket; retained storage does not grow by one object per received fragment. This adds one copy of incoming bytes into the bounded block storage. Receiving pauses at byte capacity; a count that cannot be fulfilled within the cap fails explicitly. Ancillary truncation or excess FDs fail with cleanup, never successful partial FD delivery. socket.stats reports current ownership counters without payload contents.

signal and positive integer timeoutMs apply to connect, send, and read; timeout includes queued time. Already-aborted requests have no transport side effects. A full Unix accept backlog (connect EAGAIN) remains pending with cancellable backoff from 5 to 50 ms; writable readiness alone is not treated as a completed connection. Cancelling a send before any positive byte submission removes it and preserves the connection. After partial submission, cancellation or failure destroys the connection to prevent a protocol hole. Send failures expose error.progress.bytesAccepted and error.progress.fdsSubmitted for that request; they do not prove peer consumption.

Stable FdxError.code values are ERR_FDX_ARGUMENT, ERR_FDX_LIMIT, ERR_FDX_CLOSED, ERR_FDX_ABORTED, ERR_FDX_TIMEOUT, ERR_FDX_EOF, ERR_FDX_NATIVE, ERR_FDX_ANCILLARY, ERR_FDX_PLATFORM, ERR_FDX_BINARY_MISSING, and ERR_FDX_ABI. Native details remain in cause.

Lifecycle and endpoints

end() drains accepted sends and shuts down writes, independently of reads. Peer EOF allows local writes by default. destroy() immediately invalidates native work, releases undelivered resources, and settles pending operations; it never waits for peer EOF. Both operations are idempotent. Socket sync/async disposal destroys locally. ref() / unref() cover native lifetime, fairness callbacks, and operation timers.

listen({ address }) returns a typed server. Binding completes synchronously; listener options do not include an operation signal or timeout. Use server.close() to end its lifetime. Subscribe to connection to take ownership of accepted sockets. Connections without a listener are closed. A throwing handoff closes that socket and stops the listener. server.close() stops accepting and preserves previously handed-off sockets. Server disposal closes the listener.

Core lifecycle events are end, finish, close, and optional error; operation promises are authoritative. end means observed peer write EOF, finish means local write shutdown, and close means local teardown. EOF is observed when a read requests input. The core only emits error when listeners exist.

Addresses are mutually exclusive { path: string } and { abstract: string | Uint8Array }. Filesystem paths reject embedded NUL and exceed neither the encoded sockaddr_un capacity nor UTF-8 byte limits. Abstract addresses preserve binary names. Filesystem endpoints remain caller-owned: the library never pre-unlinks or automatically removes a socket path. Create a private directory and remove that directory explicitly after closing owned sockets.

dbus-next integration

Merge this configuration into the root application package.json:

{
  "dependencies": {
    "dbus-next": "0.10.2",
    "usocket": "npm:@primafuture/[email protected]"
  },
  "overrides": {
    "usocket": "$usocket"
  }
}

Root placement matters: npm ignores overrides declared by installed dependencies. During candidate testing, use file:/absolute/path/to/primafuture-socket-fdx-1.1.0.tgz for usocket instead of the published alias. The root export includes USocket, and /compat/usocket exposes the same constructor. Enable negotiateUnixFd: true in dbus.sessionBus options (dbus-next's shipped declarations omit that option).

Wait for the bus-level connect event (successful D-Bus Hello) before the first FD-bearing application call:

const bus = dbus.sessionBus({ busAddress, negotiateUnixFd: true });
await events.once(bus, 'connect'); // Import node:events as a namespace.
await bus.call(messageWithFds);

dbus-next can flush a queued call immediately after writing authentication BEGIN. If that call already contains FDs, the daemon can still consume it through its byte-only authentication reader and disconnect. The integration test therefore waits for Hello, rather than inserting a timing delay. This prerequisite is part of the supported adapter integration. The daemon's authentication reader uses byte-only socket reads; the generic transport cannot infer D-Bus protocol readiness.

The adapter supports constructor options, one connected notification, byte/string and { data, fds } writes, callbacks, drain, synchronous counted reads, descriptor-aware unshift, writable state, and prompt destruction. It captures send ownership at write(). Accepted writes return the current soft backpressure state. A write rejected by validation or a hard limit returns false and makes the adapter non-writable immediately, with its callback and error notification delivered asynchronously. Do not retry a whole message merely because write() returned false: an accepted message is already queued. end(data, callback) validates its overload before admitting the final payload and completes the callback once after payload acceptance and write shutdown. A rejected final payload preserves its original error; invalid callbacks produce the controlled ERR_FDX_ARGUMENT path.

After both local finish and peer end, the adapter releases its socket once all buffered input has been consumed and emits close once, so public bus.disconnect() permits natural process exit. Either half may still complete first; pending writes drain before automatic closure. Peer end means observed EOF, not an empty input buffer: a partial prefix and its FDs remain available to subsequent reads. Retrying counts that cannot be satisfied at EOF emits ERR_FDX_EOF and releases undelivered resources. A synchronous unshift immediately after the last read retains returned input before automatic closure. Core sockets retain their explicit destruction/disposal contract.

It is not a complete net.Socket implementation. Read demand bounds each native receive; read(length) retains FDs, while read(length, null) returns all pending FDs through that byte boundary. unshift(data, fds) transfers those still-open numeric FDs back into library ownership and does not mutate the arrays.

Integration tests verify transitive package alias resolution and exchange real file descriptors through a private D-Bus daemon.

Build and verify

Ordinary package installation has no compilation hook, downloads, or runtime dependencies. Import is lazy and opens no sockets. Explicit socket operations load the shipped prebuild and report distinct unsupported-platform, missing-binary, or incompatible-ABI errors. The addon uses Node-API 8 and direct libuv; Node-API stability alone does not make all future Node/libuv releases supported.

From the release source archive (GCC/G++, make, Python, Linux development headers, Node and npm required):

npm ci --ignore-scripts
npm run build:native
npm run build -- --local-native
npm run typecheck
npm test
npm run test:package -- --local-native
npm pack --ignore-scripts

This explicit source fallback creates a replacement tarball for the current host. It does not claim the glibc 2.28 baseline. It requires registry access for locked build dependencies and Node headers; installed runtime use requires neither.

The source tree contains build instructions; generated dist and .node files are build outputs. The deliverables are separate: the npm tarball contains the audited prebuild, while the source archive contains the lockfile and everything needed to rebuild it.

npm run release:candidate copies the explicit source inputs listed in scripts/source-input.ts into a fresh temporary directory, verifies their hashes, installs locked build dependencies, builds the audited native artifact there, builds JavaScript/declarations, typechecks, and creates the tarball. The build is version-control agnostic: it uses neither repository metadata nor version-control commands. Files under the declared source directories are included regardless of tracking status. Inside declared source roots, hidden descendants and names node_modules, build, dist, prebuilds, or coverage are reserved: encountering one fails explicitly instead of silently omitting potentially legitimate source. Keep auxiliary data outside these roots; explicitly listed root dotfiles remain source inputs. This generic naming policy requires no version-control detection. Symbolic links and source changes during snapshot acquisition fail explicitly. The output and its source/native/tarball checksums are in .artifacts/release/. test:package always rebuilds through this isolated gate; D-Bus and matrix fixtures consume the matching candidate. The candidate is a local deliverable; no command publishes it. Source archives are built and optionally verified in staging before replacing the delivered archive. A failed verification preserves the prior matching archive/evidence pair; delivery without --verify removes any previous success evidence.

For the release baseline, Docker builds from the pinned AlmaLinux image, builds twice, compares exact native bytes, and audits dynamic symbols:

npm ci --ignore-scripts
npm run build:prebuild
npm run check
npm run test:matrix
npm run release:source -- --verify

check requires a fresh audited prebuild for source checks, then runs native compilation, package build, strict type checks, unit/native suites, a new isolated-source candidate build and installed package tests, D-Bus tests, and sanitizers. test:package requires Docker unless --local-native explicitly selects the host source-build fallback. test:dbus requires dbus-daemon; test:sanitizers requires GCC's ASan/UBSan runtime; test:matrix explicitly downloads checksummed official Node distributions. All fixtures use deadlines and own their cleanup.

The tarball includes prebuilds/linux-x64/manifest.json with exact source and binary hashes, base/image identities, compiler/package versions, glibc/libstdc++ requirements, Node-API/libuv observations, and repeated-build evidence. See architecture and evidence for the verified platform matrix, test coverage, and limitations.