Kizaki
ReferenceTypeScript

@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

On this page