Kizaki
Concepts

Compiler, Runtime, And Generated Client

Understand how Inspire, your exposed functions, and the generated TypeScript packages fit together.

The compiler bridges three things: the schema you declare in Inspire, the runtime contract the platform enforces, and the TypeScript packages your application imports. Understanding what it produces makes the rest of the platform easier to reason about.

Inspire schema + exposed functions
  -> compiler
  -> artifact.json       (runtime enforcement)
  -> schema.sql          (PostgreSQL DDL)
  -> @kizaki/schema      (typed entities for server code)
  -> @kizaki/client      (typed RPC stubs for browser code)

What The Compiler Produces

Each compilation run generates four outputs. They serve different layers of the stack, but they all derive from the same source model.

artifact.json — the runtime contract

The compiled representation of your schema, policies, and configuration. The database engine loads it at startup and uses it to enforce authorization on every query. It contains:

  • entity definitions and their fields
  • compiled policy rules (@grant and @deny declarations) in a form the engine can evaluate at query time
  • route declarations
  • schedule and effect configuration

Application code never reads this file directly. It exists for the database engine.

schema.sql — PostgreSQL DDL

The SQL statements needed to create or update the database schema: tables, columns, indexes, constraints. Migrations are built from this output. The compiler compares the current schema.sql against the previous snapshot to produce a migration plan.

@kizaki/schema — typed entity objects

Generated to .kizaki/build/schema/, this package exports a typed EntityTable object for each entity in your schema, with FieldRef properties for each field. Your server code imports these for type-safe queries:

import { Project } from "@kizaki/schema";

const projects = await query(
  select(Project)
    .where(eq(Project.ownerId, getPrincipal().userId))
);

The Project object is not a class or an ORM model. It is a typed reference that the query builder uses to construct correct SQL and that TypeScript uses to infer result types. When you add a field to the Project entity in Inspire, it appears as a new property on this object after the next compilation.

@kizaki/client — typed RPC stubs

Generated to .kizaki/build/client/, this package exports one async function for every /** @expose */ function in your application. The browser imports these directly:

import { createProject } from "@kizaki/client";

const project = await createProject("Northlight");

No fetch calls, no URL construction, no request/response typing. The generated function handles serialization, transport, and error handling.

How @expose Functions Become Client Calls

The compilation path for exposed functions is concrete and worth tracing.

You write a server function:

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

/** @expose */
export async function createProject(name: string) {
  const principal = getPrincipal();

  const [project] = await query(
    insert(Project).values({
      name,
      ownerId: principal.userId,
    })
  );

  return project;
}

The compiler scans TypeScript source files under src/ for the /** @expose */ annotation. For each annotated function, it generates a corresponding client stub in @kizaki/client that:

  1. accepts the same parameters (name: string)
  2. sends them as a structured RPC call to the app server
  3. routes the call to the function runtime, which invokes your function with the authenticated principal in async context
  4. returns the result with the correct TypeScript type

The browser code that calls createProject("Northlight") has no knowledge of how the function is implemented. It only knows the function signature. This boundary keeps the browser from depending on server internals.

The Runtime Contract

The database engine loads artifact.json at startup and holds it in memory. Every query that passes through the engine is checked against the compiled policies.

This is not advisory. There is no flag to disable it, no escape hatch in the SDK. When your server code calls query(select(Project)), the engine:

  1. identifies the principal from the request context
  2. looks up the @grant read rules for Project in the artifact
  3. constructs SQL WHERE clauses that reflect those rules
  4. executes the query with those clauses applied

If the principal has @grant read to owner, the engine adds WHERE owner_id = $principal_id. If the principal has no matching grant, the query returns zero rows. If a @deny rule matches, it takes precedence over any grant.

Write operations follow the same path. An insert, update, or delete is checked against write policies before the SQL executes. A policy violation returns an error. The write never reaches the database.

Your application code does not need to check permissions. The enforcement layer sits between your code and the database, and it cannot be bypassed from within the function runtime.

What Stays Aligned

The compiler regenerates all outputs together. When you change the schema, every downstream surface updates in the same compilation pass.

When you change...These are regenerated
An entity's fieldsartifact.json, schema.sql, @kizaki/schema entity object
A @grant or @deny ruleartifact.json (policies hot-reload, no migration needed)
An /** @expose */ function signature@kizaki/client stub for that function
A route declarationartifact.json route table
A field type (e.g., string to integer)artifact.json, schema.sql, @kizaki/schema field type

This alignment prevents the class of bugs where the database expects one shape, the server code assumes another, and the browser sends a third. Those surfaces are generated from the same source and compiled together.

During local development, the file watcher triggers recompilation on every save. The database engine hot-reloads the new artifact, the function runtime picks up code changes, and the generated packages are updated in place. There is no manual rebuild step. In VS Code, the @kizaki/client and @kizaki/schema declarations also refresh automatically once workspace trust is granted, so type navigation works without a manual compile.

On this page