kolu
Docs

How to consume a surface outside SolidJS

The .use() hooks are SolidJS. A CLI or TUI has no reactive runtime, so it reads the surface directly off the typed client. The trick that keeps a CLI small is to hold one transport-blind connection so every command is written once, whether it runs against a local unix socket or a remote box over ssh.

Hold one connection shape

Model the connection as { client, dispose } and dial it once. Local and remote differ only in which link you build; the commands downstream never branch.

type Connection = {
  client: ContractRouterClient<typeof surface.contract>;
  dispose: () => void;
};

// local: dial the daemon's unix socket
const { client, dispose } = await unixSocketLink<typeof surface.contract>({
  socketPath,
});

Call procedures with a plain await

Procedures are ordinary async calls off the client. No hooks, no subscription.

await client.surface.proc.kill({ pid });
const keys = await firstFrameOrThrow(
  await client.surface.processes.keys({}),
  "processes keys yielded no snapshot frame — link failure",
);

Iterate a stream

A cell, collection, or stream read is an async iterable — snapshot first, then deltas. Loop it and act on each frame.

for await (const frame of await client.surface.nodeLog.get({ nodeId })) {
  process.stdout.write(frame.text);
}

For a raw (non-descriptor) stream, reach for unenrolledStreamCall (@kolu/surface/client) rather than calling the procedure bare — it carries the STREAM_RETRY reconnect context, so a dropped transport re-subscribes transparently instead of ending the loop.

const frames = await unenrolledStreamCall(
  client.surface.nodeLog.get,
  { nodeId },
  { signal, onRetry: () => resetView() },
);
for await (const frame of frames) render(frame);

Drive a live board with mirrorRemoteSurface

When the CLI is a live dashboard rather than a one-shot query, fold the surface into plain callbacks with mirrorRemoteSurface (@kolu/surface/mirror) — the consume-side dual of implementSurface. You supply a sink of collection and stream callbacks; it keeps them current and hands back the procedures plus a done promise.

const { procedures, done } = mirrorRemoteSurface(
  surface,
  client,
  {
    collections: {
      processes: {
        upsert: (pid, proc) => board.set(pid, proc),
        remove: (pid) => board.delete(pid),
      },
    },
    streams: { nodeLog: { input: nodeId, onFrame: (f) => board.appendLog(f) } },
  },
  { signal },
);

await procedures.proc.kill({ pid }); // same typed procedures, mirrored
await done;