@kizaki/client
Generated async functions for calling server workflows from the browser.
@kizaki/client is the browser-side counterpart to your @expose server functions. The compiler finds every function annotated with /** @expose */ and generates a corresponding async RPC stub.
How Generation Works
Given a server function:
// server/projects.ts
import { query, insert, getPrincipal } from "@kizaki/sdk";
import { Project } from "@kizaki/schema";
/** @expose */
export async function createProject(name: string) {
const me = getPrincipal();
return query(insert(Project).values({ name, ownerId: me.id }).returning(Project.id));
}The compiler generates a client stub at .kizaki/build/client/:
import { createProject } from "@kizaki/client";The generated function has the same name, parameters, and return type. It serializes arguments, sends them to the server via /__rpc, and deserializes the response.
Calling Pattern
Client functions are plain async functions.
import { createProject, assignMember } from "@kizaki/client";
const project = await createProject("Documentation Site");
await assignMember(project.id, userId);The caller's session cookie is sent automatically. The server function runs with the caller's principal — all policy checks apply.
Error Handling
Server errors are deserialized into typed error classes.
import { createProject } from "@kizaki/client";
import { PolicyDeniedError } from "@kizaki/sdk/browser";
try {
await createProject(name);
} catch (e) {
if (e instanceof PolicyDeniedError) {
showPermissionError();
}
}Use .toUserMessage() on any SDK error for a human-readable string suitable for display.
When to Use the Client
The generated client is the right surface for operations that need server-side context:
- Mutations — creating, updating, or deleting records
- Multi-step workflows — placing an order, sending an invitation
- Permission-checked actions — anything that runs with policy enforcement
- Side effects — sending email, creating files, starting a payment checkout
For read queries that should stay live, use @kizaki/sdk/browser query builders with useQuery() instead. The client is for fire-and-forget operations, not ongoing subscriptions.
Integration with React
useMutation from @kizaki/react wraps client functions with loading state, error handling, and cache invalidation.
import { useMutation, useQuery } from "@kizaki/react";
import { select } from "@kizaki/sdk/browser";
import { createProject } from "@kizaki/client";
import { Project } from "@kizaki/schema";
const projectsQuery = select(Project).orderBy(Project.createdAt, "desc");
function CreateProjectButton() {
const create = useMutation(createProject, {
invalidate: [projectsQuery],
});
return (
<button onClick={() => create.mutate("New Project")} disabled={create.pending}>
New Project
</button>
);
}When the mutation completes, every useQuery subscription matching the invalidate list refetches automatically.
What Is Not Generated
Only functions annotated with /** @expose */ produce client stubs. Private helpers and server-internal logic are never exposed.
The compiler does not validate parameter or return types of @expose functions. Any JSON-serializable TypeScript type works. Non-serializable types (functions, class instances with methods, symbols) fail at runtime.
Related guide: Write Server Functions