Typed requests with typed()

The hand-written helpers (client.orgs, client.spaces, …) cover the common, stable surface. For any route in the API, use client.typed(method, path, init). It reads the machine-generated paths interface (the generated types, produced from the OpenAPI spec) and infers, for the exact (method, path) you pass:
which path params the route requires (filled into
{placeholders}),which query params it accepts,
the JSON body shape, and
the success response type.
The path and method both autocomplete, and the compiler rejects a body or param that doesn't match the spec.
Signature
typed<P extends keyof paths, M extends keyof paths[P] & HttpMethod>(
method: M,
path: P,
init: TypedRequestInit<paths[P][M]>,
): Promise<TypedResponse<paths[P][M]>>init carries up to four slots, each required exactly when the spec says so:
The resolved response type is the 200 body, else 201, else 202, else undefined for 204.
Example 1 — list a collection (GET with a path param)
const { spaces } = await forumulate.typed('get', '/orgs/{orgId}/spaces', {
params: { orgId },
query: { deleted: 'false', limit: '50' },
});orgId is required and type-checked; spaces is typed from the spec.
Example 2 — create a resource (POST with a typed body)
const { space } = await forumulate.typed('post', '/orgs/{orgId}/spaces', {
params: { orgId },
body: {
name: 'Docs',
slug: 'docs',
type: 'DOCS', // enum union comes from the schema
visibility: 'PUBLIC',
},
});A body field with the wrong name — or a type outside the allowed union — is a compile error.
Example 3 — write a piece of content, then publish it
// Create an item (a "piece" of a space — page, post, incident, …).
const { item } = await forumulate.typed('post', '/orgs/{orgId}/spaces/{spaceId}/items', {
params: { orgId, spaceId },
body: { title: 'Getting started', body: '# Hello\n\nWelcome.' },
});
// Publish it.
await forumulate.typed('post', '/orgs/{orgId}/spaces/{spaceId}/items/{itemId}/publish', {
params: { orgId, spaceId, itemId: item.id },
});Binary responses with raw
For endpoints that return a file rather than JSON, pass raw: true and read the Response yourself:
const res = await forumulate.typed('get', '/public/orgs/{orgId}/spaces/{spaceId}/pdf', {
params: { orgId, spaceId },
raw: true,
});
const pdf = await res.arrayBuffer();Escape hatch: request()
Everything above delegates to a low-level, untyped method you can call directly for anything the typed layer can't express:
await forumulate.request('POST', `/orgs/${orgId}/spaces/${spaceId}/items/${itemId}/publish`, {
body: {},
});request<T>(method, path, { body?, query?, raw? }) — you build the path yourself and choose the response type. It applies the same auth header and error handling as the typed calls.