Example app
The example app is a back office for Warehouse, a made-up store. It has five
Postgres tables: suppliers, products, customers, orders and a stock ledger. The
sidebar and the chat panel are hand-written; every page and chat answer is a
uicast document that a model writes with the
reference catalog. It runs at
warehouse.uicast.dev . The code is in
examples/back-office.
Deploy
Vercel creates a Neon database and asks for APP_SECRET; make one with
openssl rand -base64 32. The first deploy creates the tables and the
demo account.
Run
npm install # repo root
docker compose -f examples/back-office/docker-compose.yml up -d # local Postgres
cp examples/back-office/.env.example examples/back-office/.env.local
npm run db:push -w @uicast/back-office
npm run dev -w @uicast/back-officeIn .env.local, set APP_SECRET: it encrypts the stored OpenRouter keys.
DATABASE_URL works as is with the docker database, and OPENROUTER_MODEL is
optional. npm run db:seed -w @uicast/back-office is optional too: it
writes the @uicast demo account that logged-out visitors see.
More details are in the app’s README .
Functions
src/tools/ has one host function per REST endpoint. Each
one calls the endpoint a hand-written front end would call, and shares the
route’s schema from src/db/zod.ts:
export const listProducts = standardTool({
name: "listProducts",
description: "A page of products, by name by default.",
inputSchema: productListInput.optional(),
outputSchema: pageOf(productOutput),
execute: (input) => apiFetch(`/api/products${query(input ?? {})}`),
});The model gets only the functions over the store’s data. The endpoints for pages, entries and chats stay out, so a generated page can create an order but cannot edit a page or read a chat (Security):
// Page and entry endpoints are excluded: the page system is not the model's to mutate.
export const domainTools: StandardToolV0[] = [
listCustomers, getCustomer, createCustomer, updateCustomer, deleteCustomer,
listProducts, getProduct, createProduct, updateProduct, deleteProduct,
listOrders, getOrder, createOrder, updateOrder, deleteOrder,
listSuppliers, getSupplier, createSupplier, updateSupplier, deleteSupplier,
listStockMovements, createStockMovement,
getSalesSummary, getStockSummary,
];Pages and chats
A page streams into the component_entries table, one row per entry
(Persisting as it streams). Opening it
later is a query and a render. An edit is another generation that re-emits
only the entries it changes (key). A chat answer carries its
entries in uicast fences, which the Streamdown plugin renders.