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

lixnet

v1.3.12

Published

RPC over HTTP and WebSockets, simplified.

Downloads

452

Readme

Intro

Lixnet is a tiny yet powerful TypeScript-first RPC library for teams who want a small, explicit RPC layer with excellent typing, without codegen or framework lock-in.

If you like the ergonomics of tRPC or Next.js Server Actions, but want something that:

  • stays close to the Fetch platform (Request/Response)
  • works in Bun / Next.js / Workers
  • keeps the protocol simple and inspectable

Lixnet is a good fit.

It gives you:

  • HTTP RPC via fetch (LixnetServer + LixnetClient)
  • WebSocket events + callbacks (LixnetPeer)
  • Optional Zod validation on the server
  • Next.js-like request.cookies() / request.headers() ergonomics inside handlers (without pulling in Next)

It’s designed to be minimal: no codegen, no schema registry, no client bundler magic.

Table of contents

Quickstart (HTTP RPC)

Install:

bun add lixnet zod

Other package managers:

pnpm add lixnet zod
npm i lixnet zod

1) Define an Events type

This is the contract shared by server and client.

type Events = {
    greet: (input: { name: string }) => Promise<string>;
};

2) Create the RPC server

Register handlers with optional Zod schemas, then expose an HTTP endpoint that forwards the incoming Request to server.handle(...).

import { z } from "zod";
import { LixnetServer } from "lixnet";

type Events = {
    greet: (input: { name: string }) => Promise<string>;
};

export const server = new LixnetServer<Events>({
    debugLog: false,
});

server.on({
    event: "greet",
    schema: z.object({ name: z.string() }),
    handler: async ({ name, request, response }) => {
        // Optional: read request cookies/headers (Next-like API)
        const userAgent = request.headers().get("user-agent");
        if (userAgent) response.header("x-user-agent", userAgent);

        return `Hello, ${name}!`;
    },
});

3) Wire the HTTP endpoint

Lixnet uses the Fetch standard Request/Response, so wiring is always “forward Request into server.handle(...)”.

Framework adapters (server wiring)

Bun

import { server } from "./rpc/server";

Bun.serve({
    port: 3000,
    async fetch(req) {
        const url = new URL(req.url);
        if (req.method === "POST" && url.pathname === "/rpc") {
            return server.handle(req);
        }
        return new Response("Not found", { status: 404 });
    },
});

Next.js Route Handler

export async function POST(req: Request) {
    return server.handle(req);
}

Cloudflare Workers / standard Fetch handler

export default {
    async fetch(req: Request) {
        const url = new URL(req.url);
        if (req.method === "POST" && url.pathname === "/rpc") {
            return server.handle(req);
        }
        return new Response("Not found", { status: 404 });
    },
};

4) Create the RPC client

import { LixnetClient } from "lixnet";

type Events = {
    greet: (input: { name: string }) => Promise<string>;
};

const client = new LixnetClient<Events>({ rpcUrl: "/api/rpc" });

const message = await client.call("greet", { name: "World" });

If you need auth/cookies, pass fetch options:

await client.call(
    "greet",
    { name: "World" },
    { credentials: "include", headers: { Authorization: "Bearer ..." } },
);

Server: LixnetServer

Registering events

server.on(...) accepts either one registration object or an array.

  • event: string key matching your Events type
  • schema (optional): Zod schema to validate input
  • handler: receives validated input plus { request, response } injections

Example registering multiple events:

server.on([
    {
        event: "greet",
        schema: z.object({ name: z.string() }),
        handler: async ({ name }) => `Hello, ${name}!`,
    },
    {
        event: "health",
        handler: async () => ({ ok: true as const }),
    },
]);

Handling requests

server.handle(request) expects the request body to be JSON:

{ "event": "someEvent", "input": { "...": "..." } }

Responses are JSON and shaped as either:

{ "data": "..." }

or

{ "error": "..." }

Server configuration

new LixnetServer({ ... }) supports:

  • defaultHeaders: headers included on every response (unless deleted later)
  • formatter: customize how LixnetResponse becomes a Response
  • logger + debugLog: currently used for invalid JSON logging

Example:

const server = new LixnetServer<Events>({
    debugLog: true,
    defaultHeaders: {
        "access-control-allow-origin": "*",
    },
});

Client: LixnetClient

client.call(event, input, options?):

  • sends a POST with { event, input }
  • throws Error(...) when the response contains { error }
  • otherwise returns { data }’s value

Client creation

const client = new LixnetClient<Events>({
    rpcUrl: "https://api.example.com/rpc",
});

Handlers: request and response

Handler signature is:

  • your validated input fields
  • plus:
    • request: a Request enhanced with request.cookies() and request.headers()
    • response: a LixnetResponse you can use to stage headers/cookies/status

Common operations:

  • Set status: response.code(201)
  • Set header: response.header("X-Foo", "bar")
  • Delete header: response.deleteHeader("x-foo")
  • Set cookie: response.cookie("session", "abc", { httpOnly: true, path: "/" })
  • Delete cookie: response.deleteCookie("session", { path: "/" })

request.cookies() / request.headers() also support “read your writes” inside the handler because they merge staged response mutations.

WebSockets: LixnetPeer

LixnetPeer is a typed event dispatcher for WebSockets with:

  • fire-and-forget events
  • optional callbacks (request/response style over WS)
  • optional chunking for large payloads (see contributor docs for protocol details)

Minimal sketch:

import { LixnetPeer } from "lixnet";

type ClientToServer = {
    ping: (input: { t: number }) => Promise<{ ok: true }>;
};

type ServerToClient = {
    notify: (input: { message: string }) => void;
};

const peer = new LixnetPeer<ServerToClient, ClientToServer>();

peer.on("ping", async ({ t, socket }) => {
    return { ok: true };
});

// In your WS message handler:
// peer.handle({ data: event.data, socket: ws });

WebSocket setup (typical pattern)

const peer = new LixnetPeer<ServerToClient, ClientToServer>();
peer.setSocket(ws);

ws.addEventListener("message", (event) => {
    peer.handle({ data: event.data, socket: ws });
});

Callbacks (request/response over WS)

await peer.call(
    "ping",
    { t: Date.now() },
    {
        callback: (result) => {
            // result is typed as Awaited<ReturnType<ClientToServer["ping"]>>
            console.log(result.ok);
        },
    },
);

Large payloads (chunking)

If messages can exceed your runtime’s WS frame limits, enable chunking:

peer.setTransmissionLimit(64_000); // bytes-ish (string length)
peer.setTransmissionChunksLimit(20); // currently stored, not enforced by sender

API reference

Exports

From src/exports.ts:

  • LixnetServer
  • LixnetClient
  • LixnetPeer
  • LixnetResponse

Types:

  • LXN_ServerClient_EventType
  • LXNServerHandler
  • FunctionInput
  • LXN_ServerClient_Request (alias of LixnetRequest)
  • LixnetRequest, LixnetCookies, LixnetHeaders

Contributor docs

License

MIT