14. Web apps: no network boundary to write

// ucm: namespace app
import { readFileSync } from "node:fs";

export async function hostCount(): Promise<number> {       // uses node:fs → runs on the server
  return readFileSync("/etc/hosts", "utf8").split("\n").length;
}
export const render = (n: number) => `hosts: ${n}`;        // runs anywhere
export async function main() {                             // uses document → runs in the browser
  document.querySelector("#app")!.textContent = render(await hostCount());
}

ucm dev app.main serves this through Vite 8. Nothing is annotated: there's no "use server", no routes, no fetch calls and no API layer. ucm update in another shell hot-reloads the browser. For production, see production.

14.1 How placement is inferred

ucm view shows the placement of every definition, and why.

14.2 What this guarantees

14.3 Plain data crosses the wire

JSON plus Date, Map, Set, BigInt, undefined, NaN/±Infinity and -0. Functions, class instances, symbols and cycles are refused with a message saying what couldn't be sent, rather than silently turning into {}. The browser and the server share one codec (packages/cli/src/codec.ts).

14.4 Arguments are checked against the parameter types

A server function the browser can call is a public endpoint, and TypeScript types are gone at runtime. So ucm derives a runtime schema from each server function's parameters (objects, arrays, tuples, unions, literals, dates, maps, sets, recursive types). Calls with wrong types, missing fields, unexpected extra fields, or too many arguments are refused with a 400 that says what's wrong, before the function runs.

14.5 CSRF

Server functions read cookies, so calls must be same-origin, must not be cross-site (Sec-Fetch-Site), and must send JSON, which rules out plain HTML form posts. HTTP routes are for other callers and skip these checks.

14.6 Module-level state is placed by who uses it

That follows from inference, not annotation, and every app report shows it:

Only fresh mutable objects count (new …, [], {}); as const, frozen and computed values don't. For state that must survive restarts, use a table.

built from 5890e54f