kolu
Docs

How to test a surface

directLink is the identity transport: it wraps the served router and invokes handlers in-process, with no socket and no subprocess. That makes it the tool for testing a surface — the client you drive in the test is the exact typed client a socket consumer would hold.

Build an in-process client

Implement the surface, flatten the fragment, and wrap it in directLink. Pass it bare — directLink is the one link surfaceClient accepts without a watchdog, because its transport is always-live.

function makeTestClient() {
  const { router } = implementSurface(surface, {
    channel: inMemoryChannelByName(),
    cells: { load: { store: inMemoryStore(ZERO) } },
    collections: {
      processes: {
        readAll: () => table,
        upsert: (pid, proc) => {
          table.set(pid, proc);
        },
        remove: (pid) => {
          table.delete(pid);
        },
      },
    },
    streams: {
      nodeLog: {
        source: async function* (nodeId) {
          yield { kind: "snapshot", text: `opened ${nodeId}`, done: false };
        },
      },
    },
    events: { autosave: {} },
    procedures: {
      proc: {
        kill: async ({ input, ctx }) => {
          ctx.collections.processes.remove(input.pid);
          return { ok: true };
        },
      },
    },
  });
  // The raw typed client — the wire client, in-process. `implementSurface`
  // already returns a ready `{ router }`, so no re-wrap; pass it bare, because
  // directLink is the one link `surfaceClient` accepts without a watchdog.
  return directLink<typeof surface.contract>(router);
}

Assert on a snapshot

A cell or stream read yields a snapshot first, then deltas, as an async iterable. To assert on the current value, take the first frame. firstFrameOrThrow (@kolu/surface/first-frame) does exactly that, and throws if the stream closes empty — so a broken read fails the test instead of asserting on undefined.

const client = makeTestClient();
const load = await firstFrameOrThrow(
  await client.surface.load.get({}),
  "load cell yielded no snapshot frame — link failure",
);
expect(load).toEqual(ZERO);

Drive a procedure and assert the effect

Procedures are plain awaited calls. Call one, then read the affected cell or collection back and assert the new snapshot.

await client.surface.proc.kill({ pid: 4321 });

const alive = await firstFrameOrThrow(
  await client.surface.processes.keys({}),
  "processes keys yielded no snapshot frame — link failure",
);
expect(alive).not.toContain(4321);

Read a run of stream frames

When the value under test is a stream that emits over time, iterate it and collect the frames you care about. Because there is no socket, delivery is deterministic — no timers, no flushing.

const frames: LogFrame[] = [];
for await (const frame of await client.surface.nodeLog.get({
  nodeId: "node-1",
})) {
  frames.push(frame);
  if (frame.done) break;
}
expect(frames[0]?.kind).toBe("snapshot");