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

@k13engineering/nfs-server

v0.0.3

Published

Platform agnostic NFSv3 and NFSv4 server on top of fsapi

Readme

nfs-server

NFSv3 and NFSv4.0 servers for any fsapi filesystem, written in TypeScript without platform dependencies. You provide the network as a small interface, so the servers run wherever you can supply TCP (and optionally UDP) sockets.

Note: this project is mostly AI coded. Its code, tests and documentation were largely written by an AI assistant, and reviewed and directed by a human maintainer.

npm install @k13engineering/nfs-server @k13engineering/fsapi

Example

import { createRamFileSystem } from "@k13engineering/fsapi";
import { createNfsV3Server, createNfsV4Server } from "@k13engineering/nfs-server";
import { createNodeNetwork } from "./node-network.ts";

const fileSystem = createRamFileSystem();
const network = createNodeNetwork();

const v3 = createNfsV3Server({ fileSystem, network });
// or
const v4 = createNfsV4Server({ fileSystem, network, port: 2050 });

// later
v3.close();
v4.close();

Mount with mount -t nfs -o vers=3 server:/ /mnt or mount -t nfs -o vers=4.0 server:/ /mnt.

The network interface

The servers never open sockets themselves. They call listenTcp, and optionally listenUdp, of a TNetworkInterface:

type TNetworkInterface = {
  listenTcp: (args: {
    port: number,
    onConnection: (args: { connection: TTcpConnection }) => TTcpConnectionHandler
  }) => { close: () => void };

  listenUdp?: (args: {
    port: number,
    onDatagram: (args: { datagram: TUdpDatagram }) => void
  }) => { close: () => void };
};

A connection gives the addresses of both ends plus write and close. The returned handler receives onData and onClose. A datagram carries its data, the sender, and a reply function.

Listening is synchronous and never fails, and so are creating and closing the servers. Connections and datagrams may arrive as soon as the network is ready. A network that cannot listen on a port, e.g. because it is in use, reports that its own way, as only the platform knows how. The servers call the close of each listener once. Their own close stops listening and closes open connections, and can be called more than once.

An adapter for Node.js:

import net from "node:net";
import dgram from "node:dgram";
import type { TNetworkInterface } from "@k13engineering/nfs-server";

// onError gets errors of the listening sockets, e.g. EADDRINUSE; by default they are thrown, which ends the process
const createNodeNetwork = ({
  host = "0.0.0.0",
  onError = (error: Error) => {
    throw error;
  }
} = {}): TNetworkInterface => {
  return {
    listenTcp: ({ port, onConnection }) => {
      const server = net.createServer((socket) => {
        const handler = onConnection({
          connection: {
            remoteAddress: socket.remoteAddress ?? "",
            remotePort: socket.remotePort ?? 0,
            localAddress: socket.localAddress ?? "",
            localPort: socket.localPort ?? 0,
            write: ({ data }) => {
              socket.write(data);
            },
            close: () => {
              socket.destroy();
            },
          }
        });
        socket.on("data", (data) => {
          handler.onData({ data: new Uint8Array(data.buffer, data.byteOffset, data.byteLength) });
        });
        socket.on("close", () => {
          handler.onClose();
        });
        socket.on("error", () => {
          // followed by close
        });
      });
      server.on("error", onError);
      server.listen(port, host);
      return {
        close: () => {
          server.close();
        }
      };
    },
    listenUdp: ({ port, onDatagram }) => {
      const socket = dgram.createSocket("udp4");
      socket.on("message", (data, remote) => {
        onDatagram({
          datagram: {
            data: new Uint8Array(data.buffer, data.byteOffset, data.byteLength),
            remoteAddress: remote.address,
            remotePort: remote.port,
            localAddress: undefined,
            reply: ({ data: reply }) => {
              socket.send(reply, remote.port, remote.address);
            },
          }
        });
      });
      socket.on("error", onError);
      socket.bind(port, host);
      return {
        close: () => {
          socket.close();
        }
      };
    },
  };
};

Options

Both servers take:

| Option | Default | Description | | --- | --- | --- | | fileSystem | required | The fsapi filesystem to export, from its root. Use fsapi's createChroot to export a subdirectory, or createReadOnlyFileSystem. | | network | required | The network interface. | | port | 2049 | The port the server listens on. | | readOnly | false | Refuses all modifications with ROFS. | | rootSquash | true | Maps requests of uid 0 to the anonymous user. | | allSquash | false | Maps requests of all users to the anonymous user. | | anonUid, anonGid | 65534 | The anonymous user. | | maxReadSize, maxWriteSize | 1048576 | The largest READ and WRITE transfers in bytes. | | isClientAllowed | all | ({ address }) => boolean. Requests of other clients get an authentication error. | | clock | Date.now | The current time in nanoseconds. It drives time stamps set to "now" and NFSv4 leases, so tests can control it. | | logger | none | ({ message, error }) => void, called when a procedure fails unexpectedly. |

createNfsV3Server also takes:

| Option | Default | Description | | --- | --- | --- | | exportPath | "/" | The path clients mount with the MOUNT protocol. | | portmapperPort | 111 | The port of the built-in port mapper (rpcbind), or false if another port mapper runs on the host. | | udp | true | Also serves UDP, if the network supports it. |

createNfsV4Server also takes:

| Option | Default | Description | | --- | --- | --- | | leaseTime | 90 | Seconds after which a client that did not renew its lease loses its opens and locks. |

What is served

  • NFSv3 (RFC 1813) serves all 22 procedures. The MOUNT protocol version 3 runs on the same port, with no need for mountport options. Version 1 is also offered, for tools that list exports and mounts. The network lock manager NLM v4 and the status monitor NSM support fcntl locks. A port mapper (portmap v2, rpcbind v3 and v4) points clients to all of them.
  • NFSv4.0 (RFC 7530) serves all operations except delegations and named attributes. That covers client ids and leases, open and lock owners with sequence ids and replay protection, share reservations, and byte-range locks.
  • Permissions follow POSIX, checked against the mode, owner and group of each node and the AUTH_SYS credentials of the request. As on Linux, owners may read and write their own files regardless of the mode.

Limitations

  • Protocol versions: NFSv4.1 and 4.2 are not supported. Clients fall back to 4.0, Linux does this automatically. NFSv2 is not supported.
  • Security: only AUTH_SYS and AUTH_NONE. No RPCSEC_GSS (Kerberos). NFSv4 owners are numeric, as with nfs4_disable_idmapping.
  • Server state: nothing survives a restart. File handles become stale (clients have to remount), locks are lost, and there is no grace period for reclaiming them.
  • Locking:
    • The server cannot call clients back. There are no NFSv4 delegations, and blocked lock requests are not granted by callback, so clients poll for them. The Linux NLM client polls every 30 seconds.
    • Locks are advisory and only known to the server instance. NFSv3 and NFSv4 servers do not see each other's locks, other users of the fsapi filesystem do not see them at all, and READ and WRITE do not check them.
  • Exports: there is only one export per server. NFSv4 exports the filesystem root, exportPath applies to NFSv3 only.
  • Unsupported features: ACLs, named attributes, referrals and quotas.
  • Concurrency: requests are processed one after another, which keeps the state consistent but gives no parallelism.
  • Memory: the file handle table keeps an entry for every node a client has seen.
  • File names: names that are not valid UTF-8 are refused.

Development

npm ci
npm run build
npm run type-check
npm run test
npm run lint

License

LGPL-2.1-only