Server functions v0.8

A module with a top-of-file "use server" directive becomes an RPC boundary. Its exports run only on the server; the browser gets a tiny fetch proxy.

Write one

// actions/todos.ts
"use server";

import { getContext } from "@nowaki-dev/runtime/server/functions.mjs";

const store: string[] = [];

export async function addTodo(text: string) {
  store.push(text.trim());
  return store.slice();
}

export async function whoami() {
  const ctx = getContext();              // request context, RPC-time only
  return ctx?.cookies?.user ?? "anonymous";
}

Call it from an island

Import and call it like a normal async function. The browser only ships the proxy.

// islands/Todos.tsx
import { useState } from "preact/hooks";
import { addTodo } from "../actions/todos.ts";

export default function Todos({ initial = [] }) {
  const [todos, setTodos] = useState(initial);
  return <button onClick={async () => setTodos(await addTodo("new"))}>add</button>;
}

On the server you can call the same function directly (no HTTP) — e.g. from a routeloader — so server-to-server calls stay in-process.

How it works

Treat each server function like a public HTTP endpoint: validate its arguments(they arrive as JSON from the client) and check authorization with getContext(). Errors return only error.message to the client, never the stack.

Authoring rules