Generate a typed client
@grundlag/client gives an application end-to-end type safety against a specific
host: the exact providers it serves, with the exact input and output shapes of their actions
and entities. It does this by reading the host’s manifest — its self-description — and
compiling it into TypeScript. This guide covers that generation step; the two guides after it
use what it produces.
What generation produces
Section titled “What generation produces”A host publishes a manifest at GET /api/client/schema: for every provider, its actions,
entities, and events, each with metadata, JSON Schemas, and (for actions) the function name
the server registered it under. generateClientTypes fetches that manifest and compiles it
into one source file that exports two things:
| Export | Kind | Used by |
|---|---|---|
ApiSchema |
a compile-time type |
createClient<ApiSchema> — the typed proxy client |
apiManifest |
a runtime const |
client.toTools(apiManifest) — the runtime tool set |
ApiSchema is pure compile-time contract — nothing of it exists at runtime. apiManifest is
the runtime metadata a sandbox or agent needs to reconstruct callable tools. Both come from
the same manifest, so they never drift.
Write a generation script
Section titled “Write a generation script”Generation is a step you run against a live server and commit the output of — the same shape as generating types from an OpenAPI document or a database schema. Point it at a running host, fetch, compile, write:
import { writeFile } from 'node:fs/promises';import { generateClientTypes } from '@grundlag/client';
const source = await generateClientTypes({ url: 'http://localhost:3800' });await writeFile('./src/client.generated.ts', source);Run this whenever the host’s providers change, and check the generated file into your repo so consumers get the current types without a server running.
Generating against an in-process host
Section titled “Generating against an in-process host”If you own the server, you don’t need a separately running process — boot it in the script on an ephemeral port, generate, and shut it down. This keeps the types generated from exactly the providers your code registers:
import { writeFile } from 'node:fs/promises';import { generateClientTypes } from '@grundlag/client';
import { createMyServer } from './server.js';
const server = await createMyServer();await server.listen({ port: 0, host: '127.0.0.1' });try { const address = server.server.address(); if (!address || typeof address === 'string') { throw new Error('Expected the server to be listening on a TCP port'); } const source = await generateClientTypes({ url: `http://127.0.0.1:${address.port}` }); await writeFile('./src/client.generated.ts', source);} finally { await server.close();}Options
Section titled “Options”generateClientTypes takes the manifest source plus a couple of output controls:
| Option | Default | Description |
|---|---|---|
url |
— | Base URL of the host, e.g. http://localhost:3800. No trailing slash. |
prefix |
/api |
Route prefix the host mounts its routes under. |
fetch |
global fetch |
A fetch implementation to use. |
headers |
— | Headers to send with the manifest request — e.g. an auth token if the host is protected. |
typeName |
ApiSchema |
Name of the generated root type. |
bannerComment |
a default | Banner prepended to the output. |
If you only need the raw manifest — for example to build tools without generating a type file
— fetchClientManifest returns it directly with the same source options.
Next steps
Section titled “Next steps”- Call actions and search entities — use
ApiSchemato build a fully typed client for application code. - Run agents and sandboxes on the client
— use
apiManifestto drive a sandbox or agent over HTTP-backed tools.