The exact shape of the contract, inferred from the argument.
A record of named groups, each containing named endpoints.
The same object, frozen and typed as Readonly<T>.
import { defineApi, endpoint, route } from '@cleverbrush/server/contract';
import { array, number, object, string } from '@cleverbrush/schema';
const TodoSchema = object({ id: number(), title: string(), completed: boolean() });
const todosResource = endpoint.resource('/api/todos');
const ById = route({ id: number().coerce() })`/${t => t.id}`;
export const api = defineApi({
todos: {
list: todosResource.get()
.query(object({ page: number().optional(), limit: number().optional() }))
.responses({ 200: array(TodoSchema) }),
get: todosResource.get(ById)
.responses({ 200: TodoSchema }),
create: todosResource.post()
.body(object({ title: string() }))
.responses({ 201: TodoSchema }),
},
auth: {
login: endpoint.post('/api/auth/login')
.body(object({ email: string(), password: string() }))
.responses({ 200: object({ token: string() }) }),
},
});
Defines a typed API contract with one level of grouping.
The returned object is the single source of truth for both server and client. The server imports it and extends each endpoint with
.authorize(),.inject(), and OpenAPI metadata. The client passes it tocreateClient()from@cleverbrush/clientto obtain a fully typed HTTP client.At runtime the contract (and each group within it) is frozen with
Object.freezeto prevent accidental mutation — endpoint builders are immutable by design, so every.body()/.query()/ etc. call already returns a new builder.