# @kuratchi/js — Full Framework Reference
Project: KuratchiJS
Package: @kuratchi/js
Machine schema: /_assets/kuratchi-js/llms.json
Category: Cloudflare Workers-native web framework
This file is the complete reference for building apps with @kuratchi/js.
It covers routing, template syntax, server actions, Durable Objects, Agents,
progressive enhancement, error handling, runtime APIs, and configuration.
## 1) What KuratchiJS is
KuratchiJS is a Cloudflare Workers-native framework.
It compiles HTML routes in src/routes into .kuratchi output used by Wrangler.
Key capabilities:
- File-based routing from HTML files
- Server-first execution model — top-level
for (const item of items) {
{item.title}
}
The $database/ alias resolves to src/database/. Any tsconfig path alias works.
### Layout file
src/routes/layout.html wraps every page. Use where page content renders:
My App
## 5) Template syntax
### Interpolation
{title}
{@html bodyHtml}
{@raw trustedHtml}
### Conditionals (inline JavaScript if/else)
if (items.length === 0) {
Nothing here yet.
} else {
{items.length} items
}
if (user.role === 'admin') {
Admin panel
}
### Loops (inline JavaScript for)
for (const item of items) {
{item.title}
}
for (const [i, item] of items.entries()) {
{i + 1}
{item.title}
}
### Components
Import .html components from src/lib/ or packages:
Live
### Client reactivity ($:)
Inside client/browser
Block form:
Rules:
- $: is the only browser-side execution primitive in a route template
- Runs in browser scripts in template markup, NOT in the top server
The action function receives raw FormData:
import { ActionError } from '@kuratchi/js';
export async function addItem(formData: FormData): Promise {
const title = (formData.get('title') as string)?.trim();
if (!title) throw new ActionError('Title is required');
// write to DB...
}
### Redirect after action
import { redirect } from '@kuratchi/js';
export async function createItem(formData: FormData): Promise {
const id = await db.items.insert({ title: formData.get('title') });
redirect(`/items/${id}`);
}
## 7) Error handling
### Action errors
Throw ActionError for user-facing validation messages:
import { ActionError } from '@kuratchi/js';
export async function signIn(formData: FormData) {
const email = formData.get('email') as string;
const password = formData.get('password') as string;
if (!email || !password) throw new ActionError('Email and password are required');
}
In the template, the action's state object is available under its function name:
State shape: { error?: string, loading: boolean, success: boolean }
- actionName.error — set on ActionError throw, cleared on next success
- actionName.loading — set by client bridge during submission
- actionName.success — reserved for future use
Throwing a plain Error shows a generic "Action failed" message in production.
### Load errors (PageError)
Throw PageError from a route's load scope:
import { PageError } from '@kuratchi/js';
const post = await db.posts.findOne({ id: params.id });
if (!post) throw new PageError(404);
if (!post.isPublished && !currentUser?.isAdmin) throw new PageError(403);
PageError renders matching custom error pages (src/routes/404.html, etc.) or a built-in fallback.
throw new PageError(404);
throw new PageError(403, 'Admin only');
throw new PageError(401, 'Login required');
## 8) Progressive enhancement
### data-action — fetch action (no page reload)
The action function receives the args array as individual arguments:
export async function deleteItem(id: number): Promise {
await db.items.delete({ id });
}
### data-refresh — partial refresh
for (const item of items) {
{item.title}
}
### data-get — client-side navigation
Click to navigate
### data-poll — polling
{status}
### data-select-all / data-select-item — checkbox groups
for (const todo of todos) {
}
## 9) Durable Object RPC
### Function mode (recommended)
Create .do.ts files. Exported functions become RPC methods.
Use this.db, this.env, and this.ctx inside those functions.
// src/server/auth/auth.do.ts
import { redirect } from '@kuratchi/js';
export async function getOrgUsers() {
const result = await this.db.users.orderBy({ createdAt: 'asc' }).many();
return result.data ?? [];
}
export async function createOrgUser(formData: FormData) {
const email = String(formData.get('email') ?? '').trim().toLowerCase();
if (!email) throw new Error('Email is required');
await this.db.users.insert({ email, role: 'member' });
redirect('/settings/users');
}
Import RPC methods from $durable-objects/:
Optional lifecycle exports (not exposed as RPC):
- export async function onInit()
- export async function onAlarm(...args)
- export function onMessage(...args)
### Class mode (optional)
import { kuratchiDO } from '@kuratchi/js';
export default class NotesDO extends kuratchiDO {
static binding = 'NOTES_DO';
async getNotes() {
return (await this.db.notes.orderBy({ created_at: 'desc' }).many()).data ?? [];
}
}
Declare in kuratchi.config.ts and wrangler.jsonc. The compiler exports DO classes from .kuratchi/worker.js.
## 10) Agents
Kuratchi treats src/server/**/*.agent.ts as first-class Worker exports.
- The file must export a class (named or default)
- The compiler re-exports it from .kuratchi/worker.js
- .agent.ts files are NOT route modules and NOT converted into $durable-objects/* proxies
// src/server/ai/session.agent.ts
import { Agent } from 'agents';
export class SessionAgent extends Agent {
async onRequest() {
return Response.json({ ok: true });
}
}
Wrangler config needed:
{
"durable_objects": {
"bindings": [{ "name": "AI_SESSION", "class_name": "SessionAgent" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["SessionAgent"] }
]
}
## 11) Runtime APIs
Available anywhere in server-side route code:
import {
getCtx, // ExecutionContext
getRequest, // Request
getLocals, // mutable locals bag for the current request
getParams, // URL params ({ slug: 'foo' })
getParam, // getParam('slug')
redirect, // redirect('/path', 302)
goto, // same as redirect — alias
RedirectError, // redirect signal thrown by redirect()
} from '@kuratchi/js';
### Request helpers
import { url, pathname, searchParams, slug } from '@kuratchi/js/request';
const page = pathname;
const tab = searchParams.get('tab');
const postSlug = slug;
Exports: url, pathname, searchParams, slug, headers, method, params
### Framework environment
import { dev } from '@kuratchi/js/environment';
- dev is true for development builds, false for production
- Compile-time framework state, not a process env var
### Environment bindings
import { env } from 'cloudflare:workers';
const turnstileSiteKey = env.TURNSTILE_SITE_KEY || '';
Server only. Templates and client
Hello KuratchiJS
if (items.length > 0) {
for (const item of items) {
{item}
}
} else {
No items yet.
}
### Run
bun install
bun run build
bun run dev
## 15) LLM generation checklist
When generating a Kuratchi app, ensure:
- src/routes/layout.html exists and contains
- src/routes/page.html exists
- wrangler main is .kuratchi/worker.js
- package scripts include build/dev via kuratchi CLI
- Template uses inline JavaScript if/for (not #if or {#each})
- Interpolation uses {expression} (not {{ }})
- bun run build succeeds
If user asks for "basic site", generate:
- layout.html with nav and slot
- home page with template examples (if/for)
- one extra route (/about)
- one form action example