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: SurfaceFace;
  dispose: () => Promise<void>;
};

// local: dial the daemon's unix socket, then re-nest the flat wire tags into
// `client.surface.<member>.<verb>`. Every link factory returns the same
// `{ dispatch, dispose }` pair, so swapping transport is a one-line change.
const link = await unixSocketLink({ group: surface.group, socketPath });
const client = buildSurfaceFace(surface, link.dispatch);
const dispose = () => link.dispose();

Call a procedure

A procedure call off the client is an Effect — a description of the call, not a request already in flight. No hooks, no subscription. Compose it into the command’s own program and run that once, at the CLI’s main; then a Ctrl-C, a deadline, or a lost race reaches the in-flight call instead of leaving it running unobserved.

// A unary verb is a lazy `Effect`; a streaming verb is a lazy `Stream`, and a
// snapshot-then-deltas member opens with its snapshot — so `firstFrameOrThrow`
// IS the one-shot read, and it interrupts the subscription as soon as that frame
// lands. Nothing below has dispatched yet: this is one description, built.
const readKeys = Effect.gen(function* () {
  yield* kill({ pid });
  return yield* firstFrameOrThrow(
    processKeys(undefined),
    "processes keys yielded no snapshot frame — link failure",
  );
});

Iterate a stream

A cell, collection, or stream read is a lazy Stream — snapshot first, then deltas. Running it is the subscription; loop it and act on each frame.

// Consume every frame. Running the stream IS the subscription; interrupting the
// fiber (or the effect completing) tears the wire subscription down — there is
// no `AbortSignal` to thread and none to forget.
const tailLog = Stream.runForEach(nodeLog(nodeId), (frame) =>
  Effect.sync(() => 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.

// The framework's per-subscription RETRY FENCE, without enrolling the stream in
// any `client.health()` fact: a transport drop re-subscribes transparently
// (forever), a DECLARED error never does. `onRetry` fires between the failed
// attempt's last frame and the next attempt's first — clear the view there.
const fenced = unenrolledStreamCall(nodeLog, nodeId, {
  onRetry: () => resetView(),
});
const renderLog = Stream.runForEach(fenced, (frame) =>
  Effect.sync(() => 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: (p, proc) => board.set(p, proc),
        remove: (p) => board.delete(p),
      },
    },
    streams: { nodeLog: { input: nodeId, onFrame: (f) => board.appendLog(f) } },
  },
  // The mirror's own cancellation vocabulary for non-Effect callers — it is
  // translated into ONE fiber interrupt at this edge.
  { signal },
);

// The mirrored procedures are the SAME shape the face hands back — an `Effect` —
// so a forwarded call composes into the program exactly like a direct one.
const killThroughMirror = procedures.proc.kill({ pid });