How to choose a link
A link is how a client reaches a served contract. Every link produces the same typed client, so the choice is purely about transport — and you can change it later without touching a single call site.
directLink is in-process and always-live, so it may be passed bare.Pick by where the server is
| Where the server runs | Link | Import |
|---|---|---|
| In the same process (tests, single-process apps) | directLink(router) |
@kolu/surface/links/direct |
| In a browser, talking to a Node server | websocketLink(ws) |
@kolu/surface/links/websocket |
| A daemon on the same machine | unixSocketLink({ socketPath }) |
@kolu/surface/links/unix-socket |
| A subprocess, or a remote box over ssh | stdioLink({ read, write }) |
@kolu/surface/links/stdio |
Match each to its serve side: nothing for directLink (it wraps the router
directly), RPCHandler(...).upgrade(ws) for the browser, serveOverUnixSocket
for the daemon, serveOverStdio for the subprocess or ssh peer.
directLinkis the identity element: feed it the router fromimplementSurface(surface, deps).routerand every call runs the handler in-process. Use it for tests and for the in-process phase of a service you will later put behind a socket.unixSocketLinkdials the socket it opens, so it is async — it returns aPromise<{ client, dispose }>. The others hand back the client synchronously.stdioLinkcarries both a local subprocess and an ssh peer; over ssh you usually drive it throughmakeSession(see Mirror over ssh) rather than wiringread/writeby hand.
Swap a link without touching call sites
Because the client type is identical across links, a swap is a one-line change at the wiring seam. A surface that started in-process for tests:
// Was: in-process
const app = surfaceClient(
surface,
directLink<typeof surface.contract>(fragment.router),
);
moves to the browser by changing only the link:
// Now: over a WebSocket, via the app layer's watchdog-backed connect
const { client } = connectSurface({ surface, url });
const app = client; // same bound hooks, same call sites
Everything downstream — app.cells.load.use(...), app.rpc.surface.proc.kill(...)
— is byte-identical. That is the point of the link abstraction: choose the
nearest transport now, and let the surface follow the code out to a socket or a
fleet later.