Server

The server side is one long-running broker and a folder of handlers. The broker reads the synced folder, decides which files are entry points, opens their ports, and — for each accepted connection — runs the access checks and then hands the ready socket to your handler. You write connection logic; the broker owns the sockets.

The filename grammar

An entry point is a top-level file whose name matches one of two patterns. Each ends in .port.js; the token before it fixes the protocol and the port.

<name>.tcp.<port>.port.js     a TCP handler on <port>
<name>.udp.<port>.port.js     a UDP handler on <port>
TokenRule
<name>A label for you — it names the handler in logs and in ctx.name. It has no effect on what is opened.
tcp / udpThe transport. Decides which contract the module must satisfy (below) and how the broker treats traffic.
<port>An integer 165535. This number is authoritative — it is exactly what the broker opens on the OS firewall.

Two handlers may not claim the same protocol and port. On a conflict the later file is refused and logged; the first to claim the pair keeps it. Only top-level files are entry points — nested files are supporting code (see Files).

The broker model

One broker process owns all listening sockets for every declared port. It runs the accept loop, optional TLS termination, and the access checks. For a TCP handler the sequence is:

accept   the broker accepts the raw connection on the port
screen   it applies the access rules — source CIDR, connection caps, rate limits
hand off it passes the ready socket to your handler's handle(conn, ctx)

So a handler only ever sees traffic that already passed the policy in front of it. You never call listen(), never run an accept loop, never set up TLS — the broker did all of that. See the manifest for how the screening rules are declared.

The handler contract

Proposed A handler is a plain module. What it must export depends on its transport.

ExportTransportCalled
handle(conn, ctx)TCPOnce per accepted connection, with a Node socket-like conn the broker has already screened.
message(msg, rinfo, ctx)UDPOnce per datagram. UDP is connectionless — there is nothing to hand off — so the broker binds the socket, checks the source, and dispatches each message.
open(ctx)bothOptional. Runs once when the handler is loaded — set up shared structures, warm a cache.
close(ctx)bothOptional. Runs once when the handler is being unloaded — release anything open acquired.

Every call receives a ctx. Proposed its fields:

FieldWhat it is
ctx.nameThe <name> from the filename.
ctx.portThe port this handler serves, as a number.
ctx.protocol"tcp" or "udp".
ctx.sharedThe broker's in-memory shared store — get/set/delete plus publish/subscribe, visible across connections and across handlers. See Shared state.
ctx.signalAn AbortSignal that fires when the handler is being shut down or reloaded — use it to stop loops and abort in-flight work.

The full contract, with edge cases, is in the Handlers reference.

Concurrency

The module is loaded once. That single instance handles many concurrent connections — the broker calls handle again for each new socket, exactly as a normal server accepts in a loop. Anything a connection needs to share with the others lives in ctx.shared, not in a per-call variable.

A TCP handler

An echo server: read from the screened socket, write the same bytes back. No listen, no TLS, no accept loop.

// echo.tcp.7000.port.js
export function handle(conn, ctx) {
  conn.setEncoding("utf8");
  conn.write(`hello from :${ctx.port}\n`);
  conn.on("data", (chunk) => conn.write(chunk));
  conn.on("error", () => conn.destroy());
}

A UDP handler

A datagram counter: bump a shared total and reply to the sender. There is no connection — you get the message, who sent it, and the shared store.

// ping.udp.9000.port.js
export async function message(msg, rinfo, ctx) {
  const n = (await ctx.shared.get("count")) ?? 0;
  await ctx.shared.set("count", n + 1);
  ctx.reply(Buffer.from(`pong #${n + 1}\n`), rinfo);
}

For UDP the broker provides a reply bound to the listening socket, so the handler answers without binding one itself.

Next steps