Kizaki
Learn

Write Server Functions

Expose server-side workflows with typed functions and let Kizaki generate the client layer for you.

Add /** @expose */ to any exported async TypeScript function under src/ to make it callable from your app. src/functions.ts is the starter file, but Kizaki scans all supported source files, validates them against your schema, and generates @kizaki/client.

The browser calls typed exposed functions. Those functions use the generated schema objects and the server-side SDK to interact with data.

A Typical Exposed Function

import { Project } from "@kizaki/schema";
import { getPrincipal, insert, query, select, eq } from "@kizaki/sdk";

/** @expose */
export async function listProjects() {
  const me = getPrincipal();
  return query(
    select(Project)
      .fields(Project.id, Project.name)
      .where(eq(Project.ownerId, me.id))
      .orderBy("createdAt", "desc"),
  );
}

/** @expose */
export async function createProject(name: string): Promise<Project> {
  const me = getPrincipal();
  const [project] = await query(
    insert(Project)
      .values({ name, ownerId: me.id })
      .returning(),
  );
  return project;
}
  • use getPrincipal() for the current user
  • use query() for reads and writes
  • use .fields() on reads to return only what the browser needs
  • keep multi-step workflows in server functions, not the frontend

During local development, @kizaki/client and @kizaki/schema refresh automatically. The VS Code extension also keeps editor types current once you grant workspace trust.

A good exposed function encodes a business action: create an order, invite a teammate, start a checkout, publish a post, rotate an API key. Think in terms of what the UI is trying to accomplish, not raw CRUD.

Transactions

When a workflow must be atomic, use getContext() and client.transaction(...) inside the server function. This keeps your browser API simple and your data consistent.

When Not To Expose A Function

Do not create exposed functions just to proxy data the browser can already read with query objects. The better split:

  • exposed functions for mutations and business workflows
  • browser query objects for read-heavy UI state

Continue with Query Data In The Browser.

On this page