The smallest command
No args, no input, no schema. The handler patches an element and the trace shows up on the right. Everything else on this page is a variation on it.
—call sitepage.tsx → ping call
<button class="btn-primary" data-on:click={cmd(ping)}>
ping the server
</button>pingbasics.commands.tsx
import { defineCommand } from "./commands";
import { log } from "./trace";export const ping = defineCommand("demo.ping", ({ patch }) => {
log("no args, no input");
patch.elements(
<code id="ping-out" class="mono" style="color:var(--aqua)">
pong @ {new Date().toISOString().slice(11, 23)}
</code>,
);
});Signals — the free channel
Datastar ships every non-underscore signal with every request, so a counter needs no args at all. The server patches the signal; data-text re-renders. No element swap.
Malformed and missing input become refusals at the boundary instead of NaN arriving in the handler — $count + $step 422s until $step holds a number, and a string $count is refused on type. Watch the refusal lines appear on the right, and expand one to see what the schema expected against what arrived.
@post('/command/demo.increment')call sitepage.tsx → counter call
<button class="btn" data-on:click={cmd(increment)}>
$count + 1
</button>
<button class="btn" data-on:click={cmd(strictIncrement)}>
$count + $step
</button>
<button class="btn" data-on:click="$step = 5">
set $step = 5
</button>
<button class="btn btn-danger" data-on:click="$count = 'oops'">
set $count = 'oops'
</button>
<button class="btn-ghost" data-on:click="$count = 0">
reset
</button>increment, strictIncrementbasics.commands.tsx
import { defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";export const increment = defineCommand(
"demo.increment",
{ input: type({ "count?": "number" }) },
({ input, signals, patch }) => {
const next = (input.count ?? 0) + 1;
// `signals` is everything the client sent; `input` is the validated slice.
log(`whole store rode along — ${Object.keys(signals).length} signals`, {
payload: { input, signalKeys: Object.keys(signals) },
});
patch.signals({ count: next });
},
);
// CASE 3 — validation failure. Both fields are required, and the page never
// defines $step, so this 422s until you set it. Missing input becomes a refusal
// at the boundary rather than NaN arriving in the handler.
export const strictIncrement = defineCommand(
"demo.strictIncrement",
{ input: type({ count: "number", step: "number" }) },
({ input, patch }) => {
log(`passed validation — patching $count → ${input.count + input.step}`, {
payload: { input },
});
patch.signals({ count: input.count + input.step });
},
);Render-time args
Which row. Known at render, baked into the URL by cmd(), decoded back to a number — not the string a query param would give you.
Row, toggle, removetodos.commands.tsx
import { Raw } from "./jsx";
import { Todo, store } from "./store";
import { cmd, dcmd, defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";export const toggle = defineCommand(
"todo.toggle",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
store().toggleTodo(args.id);
log(`args decoded ${JSON.stringify(args)} — a number, not "${args.id}"`, {
payload: { args },
});
patch.elements(TodoList(), DelegatedList(), ScopedList());
},
);
export const remove = defineCommand(
"todo.remove",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
// The client can put any id here — see the "forge" button on the page. The
// HMAC would not help; only this check does.
if (todo.owner !== store().me) {
log(`not your row — refused (owner: ${todo.owner})`, {
level: "refusal",
payload: { args, owner: todo.owner, me: store().me },
});
patch.elements(
<Flash tone="bad">
refused: row {args.id} belongs to {todo.owner}
</Flash>,
);
return;
}
store().removeTodo(args.id);
log(`authorized and removed row ${args.id}`, { payload: { args } });
patch.elements(
TodoList(),
DelegatedList(),
ScopedList(),
<Flash tone="ok">removed row {args.id}</Flash>,
);
},
);
// ── A. one command expression per row ─────────────────────────────────────────
const Row = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" data-on:click={cmd(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" data-on:click={cmd(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);+ 9 supporting declarations (Id, ScopedList, Flash, summarize, TodoList, rows, DRow, rowCost, DelegatedList)
const Id = type({ id: "number" });
/**
* The same rows again, read-only: 03 owns the row commands and 08 owns
* delegation, so this copy exists only to show the row landing where you are
* looking when you add one.
*/
export const ScopedList = (): Raw => (
<div id="todos-scoped">
<div class="rows">
{store()
.todos()
.map((t) => (
<div class={`row ${t.done ? "done" : ""}`}>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
</div>
))}
</div>
</div>
);
export const Flash = (p: { tone: string; children?: unknown }) => (
<p
id="flash"
class="mono"
style={`margin:12px 22px 0;font-size:12.5px;min-height:1.2em;color:${
p.tone === "bad" ? "var(--red)" : "var(--green)"
}`}
>
{p.children}
</p>
);
/**
* Exported, used by the view, reachable from a test — and NOT an endpoint, because
* only defineCommand() registers. Under a "use server" directive this export would be a
* public unauthenticated route.
*/
export const summarize = () =>
`${store().doneCount()}/${store().todos().length} done`;
export const TodoList = (): Raw => (
<div id="todos">
<div class="rows">{store().todos().map(Row)}</div>
<div class="card-ft">
<span>{summarize()}</span>
<span>
args encoded → <span class="enc">?a=eyJpZCI6MX0</span>
</span>
</div>
</div>
);
// ── B. one listener for the whole list ───────────────────────────────────────
// The same two operations, wired the other way. Only the id moves to the
// listener, as a rendered allowlist; the args are still encoded by cmd()'s codec
// at render time, so there is no client-side encoding and sealed commands work.
const rows = dcmd(toggle, remove);
const DRow = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" {...rows.for(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" {...rows.for(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
/** Bytes each style spends per row, measured on the real rendered markup. */
export const rowCost = (): {
perRow: number;
delegated: number;
listener: number;
} => {
const a =
` data-on:click="${cmd(toggle, { id: 1 })}"`.length +
` data-on:click="${cmd(remove, { id: 1 })}"`.length;
// measured on the attributes dcmd() actually emits, both controls
const attrs = (fn: typeof toggle | typeof remove) =>
Object.entries(rows.for(fn, { id: 1 }))
.map(([k, v]) => ` ${k}="${v}"`.length)
.reduce((x, y) => x + y, 0);
return {
perRow: a,
delegated: attrs(toggle) + attrs(remove),
listener: ` data-on:click="${rows.on}"`.length,
};
};
export const DelegatedList = (): Raw => {
const c = rowCost();
const n = store().todos().length;
return (
<div id="todos-delegated">
<div class="rows" data-on:click={rows.on}>
{store().todos().map(DRow)}
</div>
<div class="card-ft">
<span>
delegated <span class="enc">{c.delegated * n + c.listener}B</span> ·
per-row <span class="enc">{c.perRow * n}B</span> at {n} rows
</span>
<span>
{c.perRow > c.delegated
? `breaks even at ${Math.ceil(c.listener / (c.perRow - c.delegated)) + 1} rows`
: "never cheaper"}
</span>
</div>
</div>
);
};Args are forgeable — authorization is the control
Row 3 belongs to someone else, so it renders no delete button. This one hand-builds the URL anyway, exactly as an attacker would. Signing it would change nothing; the ownership check is what refuses.
call sitepage.tsx → forge call
<button class="btn btn-danger" data-on:click={cmd(remove, { id: 3 })}>
forge remove(id: 3)
</button>removetodos.commands.tsx
import { Raw } from "./jsx";
import { Todo, store } from "./store";
import { cmd, dcmd, defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";export const remove = defineCommand(
"todo.remove",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
// The client can put any id here — see the "forge" button on the page. The
// HMAC would not help; only this check does.
if (todo.owner !== store().me) {
log(`not your row — refused (owner: ${todo.owner})`, {
level: "refusal",
payload: { args, owner: todo.owner, me: store().me },
});
patch.elements(
<Flash tone="bad">
refused: row {args.id} belongs to {todo.owner}
</Flash>,
);
return;
}
store().removeTodo(args.id);
log(`authorized and removed row ${args.id}`, { payload: { args } });
patch.elements(
TodoList(),
DelegatedList(),
ScopedList(),
<Flash tone="ok">removed row {args.id}</Flash>,
);
},
);+ 11 supporting declarations (Id, toggle, ScopedList, Flash, summarize, Row, TodoList, rows, DRow, rowCost, DelegatedList)
const Id = type({ id: "number" });
export const toggle = defineCommand(
"todo.toggle",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
store().toggleTodo(args.id);
log(`args decoded ${JSON.stringify(args)} — a number, not "${args.id}"`, {
payload: { args },
});
patch.elements(TodoList(), DelegatedList(), ScopedList());
},
);
/**
* The same rows again, read-only: 03 owns the row commands and 08 owns
* delegation, so this copy exists only to show the row landing where you are
* looking when you add one.
*/
export const ScopedList = (): Raw => (
<div id="todos-scoped">
<div class="rows">
{store()
.todos()
.map((t) => (
<div class={`row ${t.done ? "done" : ""}`}>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
</div>
))}
</div>
</div>
);
export const Flash = (p: { tone: string; children?: unknown }) => (
<p
id="flash"
class="mono"
style={`margin:12px 22px 0;font-size:12.5px;min-height:1.2em;color:${
p.tone === "bad" ? "var(--red)" : "var(--green)"
}`}
>
{p.children}
</p>
);
/**
* Exported, used by the view, reachable from a test — and NOT an endpoint, because
* only defineCommand() registers. Under a "use server" directive this export would be a
* public unauthenticated route.
*/
export const summarize = () =>
`${store().doneCount()}/${store().todos().length} done`;
// ── A. one command expression per row ─────────────────────────────────────────
const Row = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" data-on:click={cmd(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" data-on:click={cmd(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
export const TodoList = (): Raw => (
<div id="todos">
<div class="rows">{store().todos().map(Row)}</div>
<div class="card-ft">
<span>{summarize()}</span>
<span>
args encoded → <span class="enc">?a=eyJpZCI6MX0</span>
</span>
</div>
</div>
);
// ── B. one listener for the whole list ───────────────────────────────────────
// The same two operations, wired the other way. Only the id moves to the
// listener, as a rendered allowlist; the args are still encoded by cmd()'s codec
// at render time, so there is no client-side encoding and sealed commands work.
const rows = dcmd(toggle, remove);
const DRow = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" {...rows.for(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" {...rows.for(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
/** Bytes each style spends per row, measured on the real rendered markup. */
export const rowCost = (): {
perRow: number;
delegated: number;
listener: number;
} => {
const a =
` data-on:click="${cmd(toggle, { id: 1 })}"`.length +
` data-on:click="${cmd(remove, { id: 1 })}"`.length;
// measured on the attributes dcmd() actually emits, both controls
const attrs = (fn: typeof toggle | typeof remove) =>
Object.entries(rows.for(fn, { id: 1 }))
.map(([k, v]) => ` ${k}="${v}"`.length)
.reduce((x, y) => x + y, 0);
return {
perRow: a,
delegated: attrs(toggle) + attrs(remove),
listener: ` data-on:click="${rows.on}"`.length,
};
};
export const DelegatedList = (): Raw => {
const c = rowCost();
const n = store().todos().length;
return (
<div id="todos-delegated">
<div class="rows" data-on:click={rows.on}>
{store().todos().map(DRow)}
</div>
<div class="card-ft">
<span>
delegated <span class="enc">{c.delegated * n + c.listener}B</span> ·
per-row <span class="enc">{c.perRow * n}B</span> at {n} rows
</span>
<span>
{c.perRow > c.delegated
? `breaks even at ${Math.ceil(c.listener / (c.perRow - c.delegated)) + 1} rows`
: "never cheaper"}
</span>
</div>
</div>
);
};Scoped input
scope: 'todo' means the handler reads the $todo.* subtree — and cmd() derives a filterSignals from the same declaration, so this click stops shipping the whole store. The page also carries $noise.a/b/c; add one and the row appears below — the same rows as 03, read-only, because 03 owns the row commands — over a line reporting what the handler read and which keys actually crossed the wire.
@post('/command/todo.add', {filterSignals: {include: /^todo\./}})call sitepage.tsx → add call
<input
placeholder="new todo"
style="flex:1"
{...{ "data-bind:todo.text": true }}
/>
<button class="btn" data-on:click={cmd(add)}>
add
</button>addtodos.commands.tsx
import { Raw } from "./jsx";
import { Todo, store } from "./store";
import { cmd, dcmd, defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";// ── scoped input ─────────────────────────────────────────────────────────────
// `scope: "todo"` means: read the $todo.* signal subtree, and have cmd() emit a
// filterSignals so the click stops shipping the entire signal store.
export const add = defineCommand(
"todo.add",
{ scope: "todo", input: type({ text: "string > 0" }) },
({ input, signals, patch }) => {
store().addTodo(input.text);
log(
`scoped input ${JSON.stringify(input)} — client sent only ${JSON.stringify(Object.keys(signals))}`,
{
payload: { input, signalKeys: Object.keys(signals) },
},
);
patch.signals({ todo: { text: "" } });
patch.elements(
TodoList(),
DelegatedList(),
ScopedList(),
// The row lands in card 03, which is scrolled off the top by the time you
// are reading card 05 — so the card also shows what it just proved.
ScopedEcho({ input, keys: Object.keys(signals) }),
);
},
);+ 13 supporting declarations (Id, toggle, remove, ScopedList, ScopedEcho, Flash, summarize, Row, TodoList, rows, DRow, rowCost, DelegatedList)
const Id = type({ id: "number" });
export const toggle = defineCommand(
"todo.toggle",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
store().toggleTodo(args.id);
log(`args decoded ${JSON.stringify(args)} — a number, not "${args.id}"`, {
payload: { args },
});
patch.elements(TodoList(), DelegatedList(), ScopedList());
},
);
export const remove = defineCommand(
"todo.remove",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
// The client can put any id here — see the "forge" button on the page. The
// HMAC would not help; only this check does.
if (todo.owner !== store().me) {
log(`not your row — refused (owner: ${todo.owner})`, {
level: "refusal",
payload: { args, owner: todo.owner, me: store().me },
});
patch.elements(
<Flash tone="bad">
refused: row {args.id} belongs to {todo.owner}
</Flash>,
);
return;
}
store().removeTodo(args.id);
log(`authorized and removed row ${args.id}`, { payload: { args } });
patch.elements(
TodoList(),
DelegatedList(),
ScopedList(),
<Flash tone="ok">removed row {args.id}</Flash>,
);
},
);
/**
* The same rows again, read-only: 03 owns the row commands and 08 owns
* delegation, so this copy exists only to show the row landing where you are
* looking when you add one.
*/
export const ScopedList = (): Raw => (
<div id="todos-scoped">
<div class="rows">
{store()
.todos()
.map((t) => (
<div class={`row ${t.done ? "done" : ""}`}>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
</div>
))}
</div>
</div>
);
/** Card 05's own read model: the scoped subtree in, the keys that crossed the wire. */
export const ScopedEcho = (p: {
input?: { text: string };
keys?: string[];
}) => (
<div id="scoped-echo" class="card-ft">
<span>
{p.input ? "handler read " : "add one — this line will show what arrived"}
{p.input ? <span class="enc">{JSON.stringify(p.input)}</span> : null}
</span>
{p.keys ? (
<span>
client sent <span class="enc">{JSON.stringify(p.keys)}</span>
</span>
) : null}
</div>
);
export const Flash = (p: { tone: string; children?: unknown }) => (
<p
id="flash"
class="mono"
style={`margin:12px 22px 0;font-size:12.5px;min-height:1.2em;color:${
p.tone === "bad" ? "var(--red)" : "var(--green)"
}`}
>
{p.children}
</p>
);
/**
* Exported, used by the view, reachable from a test — and NOT an endpoint, because
* only defineCommand() registers. Under a "use server" directive this export would be a
* public unauthenticated route.
*/
export const summarize = () =>
`${store().doneCount()}/${store().todos().length} done`;
// ── A. one command expression per row ─────────────────────────────────────────
const Row = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" data-on:click={cmd(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" data-on:click={cmd(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
export const TodoList = (): Raw => (
<div id="todos">
<div class="rows">{store().todos().map(Row)}</div>
<div class="card-ft">
<span>{summarize()}</span>
<span>
args encoded → <span class="enc">?a=eyJpZCI6MX0</span>
</span>
</div>
</div>
);
// ── B. one listener for the whole list ───────────────────────────────────────
// The same two operations, wired the other way. Only the id moves to the
// listener, as a rendered allowlist; the args are still encoded by cmd()'s codec
// at render time, so there is no client-side encoding and sealed commands work.
const rows = dcmd(toggle, remove);
const DRow = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" {...rows.for(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" {...rows.for(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
/** Bytes each style spends per row, measured on the real rendered markup. */
export const rowCost = (): {
perRow: number;
delegated: number;
listener: number;
} => {
const a =
` data-on:click="${cmd(toggle, { id: 1 })}"`.length +
` data-on:click="${cmd(remove, { id: 1 })}"`.length;
// measured on the attributes dcmd() actually emits, both controls
const attrs = (fn: typeof toggle | typeof remove) =>
Object.entries(rows.for(fn, { id: 1 }))
.map(([k, v]) => ` ${k}="${v}"`.length)
.reduce((x, y) => x + y, 0);
return {
perRow: a,
delegated: attrs(toggle) + attrs(remove),
listener: ` data-on:click="${rows.on}"`.length,
};
};
export const DelegatedList = (): Raw => {
const c = rowCost();
const n = store().todos().length;
return (
<div id="todos-delegated">
<div class="rows" data-on:click={rows.on}>
{store().todos().map(DRow)}
</div>
<div class="card-ft">
<span>
delegated <span class="enc">{c.delegated * n + c.listener}B</span> ·
per-row <span class="enc">{c.perRow * n}B</span> at {n} rows
</span>
<span>
{c.perRow > c.delegated
? `breaks even at ${Math.ceil(c.listener / (c.perRow - c.delegated)) + 1} rows`
: "never cheaper"}
</span>
</div>
</div>
);
};A form with nothing to post to
Signals-only means there is no browser form submit to fall back to, so this <form> has no action and no method — data-on:submit__prevent is the entire submit path. The element stays a form for what the platform gives you free: Enter submits, native validation runs, labels and focus order work. The schema is still what types the input, because a hand-built POST can send anything — seats is accepted as 3 or "3" and normalises to a number.
- (none yet)
@post('/command/news.subscribe', {filterSignals: {include: /^signup\./}})SignupForm, subscribesignup.commands.tsx
import { cmd, defineCommand } from "./commands";
import { log } from "./trace";
import { store } from "./store";
import { type } from "arktype";export const subscribe = defineCommand(
"news.subscribe",
{ scope: "signup", input: Signup },
({ input, patch }) => {
store().addSub(input.email, `${input.plan} ×${input.seats}`);
log(`seats arrived as a ${typeof input.seats} and validated to a number`, {
payload: { input, seatsType: typeof input.seats },
});
patch.elements(<Subscribers />);
},
);
export const SignupForm = () => {
return (
// No `action` and no `method`: nothing here ever performs a browser submit.
// The element stays a <form> for what the platform gives you for free —
// Enter submits, required/type validation runs, labels and focus order work.
<form class="stack" data-on:submit__prevent={cmd(subscribe)}>
{/* JSX attribute names cannot contain `.`, so dotted signal paths (and dotted
event modifiers like __debounce.500ms) go through a spread. */}
<input
name="email"
placeholder="you@example.com"
{...{ "data-bind:signup.email": true }}
/>
<select name="plan" {...{ "data-bind:signup.plan": true }}>
<option value="free">free</option>
<option value="pro">pro</option>
</select>
<input
name="seats"
type="number"
value="1"
min="1"
style="width:5.5rem"
{...{ "data-bind:signup.seats": true }}
/>
<button class="btn" type="submit">
subscribe
</button>
</form>
);
};+ 2 supporting declarations (Signup, Subscribers)
const Signup = type({
email: "string.email",
plan: "'free' | 'pro'",
seats: "number | string.numeric.parse",
});
export const Subscribers = () => {
const subs = store().subs();
return (
<ul id="subscribers" class="subs">
{subs.length === 0 ? <li style="color:var(--dim3)">(none yet)</li> : null}
{subs.map((s) => (
<li>
{s.email} <span class="owner">{s.plan}</span>
</li>
))}
</ul>
);
};Sealed args
A capability minted at render time and signed. The signature rides in a Command-Signature header, not the URL, so it stays out of access logs — and a cross-origin fetch cannot set it without a preflight this server never answers. The third button carries an 'admin' payload with a 'viewer' signature: it never reaches the handler.
@post('/command/admin.grantRole?a=eyJyb2xlIjoiZWRpdG9yIn0', {headers: {'Command-Signature': 'jD1EUsgclUoNfiR_-_u8jkCO20Lqecode5M4bWd-uCE'}})call sitepage.tsx → sealed call
<button class="btn" data-on:click={cmd(grantRole, { role: "editor" })}>
grant editor
</button>
<button class="btn" data-on:click={cmd(grantRole, { role: "admin" })}>
grant admin
</button>grantRoleadmin.commands.tsx
import { defineCommand, sealed } from "./commands";
import { log } from "./trace";
import { store } from "./store";
import { type } from "arktype";export const grantRole = defineCommand(
"admin.grantRole",
sealed({ args: type({ role: "'viewer' | 'editor' | 'admin'" }) }),
({ args, patch }) => {
store().setRole(args.role);
log(`sealed args verified — role is now ${args.role}`, {
payload: { args, verified: true },
});
patch.elements(<RoleBadge />);
},
);+ 1 supporting declarations (RoleBadge)
export const RoleBadge = () => (
<span id="role" class="pill">
role: <span class="role">{store().role()}</span>
</span>
);The escape hatch — el.dataset
The same rows and the same two commands as 03, wired the other way: one listener on the container, and the id read off el.dataset at click time. Both lists are live — click either and both patch, because they render the same rows.
dcmd() moves only the id to the listener, as a rendered allowlist — args are still encoded by cmd()'s codec at render time. So nothing builds URLs on the client, the call site is type-checked, and sealed() commands work here too, which a hand-rolled version cannot do: a browser has no signing key. Adopting it deleted the last app JavaScript on this page.
And note the footer — at this row count delegation is bigger. Each row still carries its own encoded args, so you only win once the listener's fixed cost is amortised. It buys bytes on long lists and costs legibility everywhere: a row's command becomes data rather than markup.
DRow, DelegatedListtodos.commands.tsx
import { Raw } from "./jsx";
import { Todo, store } from "./store";
import { cmd, dcmd, defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";const DRow = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" {...rows.for(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" {...rows.for(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
export const DelegatedList = (): Raw => {
const c = rowCost();
const n = store().todos().length;
return (
<div id="todos-delegated">
<div class="rows" data-on:click={rows.on}>
{store().todos().map(DRow)}
</div>
<div class="card-ft">
<span>
delegated <span class="enc">{c.delegated * n + c.listener}B</span> ·
per-row <span class="enc">{c.perRow * n}B</span> at {n} rows
</span>
<span>
{c.perRow > c.delegated
? `breaks even at ${Math.ceil(c.listener / (c.perRow - c.delegated)) + 1} rows`
: "never cheaper"}
</span>
</div>
</div>
);
};+ 10 supporting declarations (Id, toggle, remove, ScopedList, Flash, summarize, Row, TodoList, rows, rowCost)
const Id = type({ id: "number" });
export const toggle = defineCommand(
"todo.toggle",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
store().toggleTodo(args.id);
log(`args decoded ${JSON.stringify(args)} — a number, not "${args.id}"`, {
payload: { args },
});
patch.elements(TodoList(), DelegatedList(), ScopedList());
},
);
export const remove = defineCommand(
"todo.remove",
{ args: Id },
({ args, patch }) => {
const todo = store().findTodo(args.id);
if (!todo) return log(`no such row ${args.id}`, { level: "refusal" });
// The client can put any id here — see the "forge" button on the page. The
// HMAC would not help; only this check does.
if (todo.owner !== store().me) {
log(`not your row — refused (owner: ${todo.owner})`, {
level: "refusal",
payload: { args, owner: todo.owner, me: store().me },
});
patch.elements(
<Flash tone="bad">
refused: row {args.id} belongs to {todo.owner}
</Flash>,
);
return;
}
store().removeTodo(args.id);
log(`authorized and removed row ${args.id}`, { payload: { args } });
patch.elements(
TodoList(),
DelegatedList(),
ScopedList(),
<Flash tone="ok">removed row {args.id}</Flash>,
);
},
);
/**
* The same rows again, read-only: 03 owns the row commands and 08 owns
* delegation, so this copy exists only to show the row landing where you are
* looking when you add one.
*/
export const ScopedList = (): Raw => (
<div id="todos-scoped">
<div class="rows">
{store()
.todos()
.map((t) => (
<div class={`row ${t.done ? "done" : ""}`}>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
</div>
))}
</div>
</div>
);
export const Flash = (p: { tone: string; children?: unknown }) => (
<p
id="flash"
class="mono"
style={`margin:12px 22px 0;font-size:12.5px;min-height:1.2em;color:${
p.tone === "bad" ? "var(--red)" : "var(--green)"
}`}
>
{p.children}
</p>
);
/**
* Exported, used by the view, reachable from a test — and NOT an endpoint, because
* only defineCommand() registers. Under a "use server" directive this export would be a
* public unauthenticated route.
*/
export const summarize = () =>
`${store().doneCount()}/${store().todos().length} done`;
// ── A. one command expression per row ─────────────────────────────────────────
const Row = (t: Todo) => (
<div class={`row ${t.done ? "done" : ""}`} data-todo-id={t.id}>
<button class="tick" data-on:click={cmd(toggle, { id: t.id })}>
{t.done ? "✓" : ""}
</button>
<span class="txt">{t.text}</span>
<span class="owner">{t.owner}</span>
{t.owner === store().me ? (
<button class="ico rm" data-on:click={cmd(remove, { id: t.id })}>
✕
</button>
) : (
<span></span>
)}
</div>
);
export const TodoList = (): Raw => (
<div id="todos">
<div class="rows">{store().todos().map(Row)}</div>
<div class="card-ft">
<span>{summarize()}</span>
<span>
args encoded → <span class="enc">?a=eyJpZCI6MX0</span>
</span>
</div>
</div>
);
// ── B. one listener for the whole list ───────────────────────────────────────
// The same two operations, wired the other way. Only the id moves to the
// listener, as a rendered allowlist; the args are still encoded by cmd()'s codec
// at render time, so there is no client-side encoding and sealed commands work.
const rows = dcmd(toggle, remove);
/** Bytes each style spends per row, measured on the real rendered markup. */
export const rowCost = (): {
perRow: number;
delegated: number;
listener: number;
} => {
const a =
` data-on:click="${cmd(toggle, { id: 1 })}"`.length +
` data-on:click="${cmd(remove, { id: 1 })}"`.length;
// measured on the attributes dcmd() actually emits, both controls
const attrs = (fn: typeof toggle | typeof remove) =>
Object.entries(rows.for(fn, { id: 1 }))
.map(([k, v]) => ` ${k}="${v}"`.length)
.reduce((x, y) => x + y, 0);
return {
perRow: a,
delegated: attrs(toggle) + attrs(remove),
listener: ` data-on:click="${rows.on}"`.length,
};
};Standard Schema — bring your own validator
commands.ts imports no validator. It types args and input against the ~standard interface, so these two commands — identical rules, identical Ctx — are validated by two different libraries in the same app. Zod 4 drops into the same slot.
Same signals, same Ctx, two vendors: arktype and valibot — read off each schema's own ~standard metadata.
try a code shorter than 4 chars — both refuse, in their own words
@post('/command/coupon.valibot', {filterSignals: {include: /^coupon\./}})arkCheck, valibotCheckschema.commands.tsx
import { defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";
import { v } from "valibot";export const arkCheck = defineCommand(
"coupon.ark",
{ scope: "coupon", input: ArkCoupon },
({ input, patch }) => {
log(`arktype accepted ${JSON.stringify(input)}`, {
payload: { vendor: "arktype", input, percentType: typeof input.percent },
});
patch.elements(
<Verdict vendor="arktype" ok={true} detail={JSON.stringify(input)} />,
);
},
);
export const valibotCheck = defineCommand(
"coupon.valibot",
{ scope: "coupon", input: ValCoupon },
({ input, patch }) => {
log(`valibot accepted ${JSON.stringify(input)}`, {
payload: { vendor: "valibot", input, percentType: typeof input.percent },
});
patch.elements(
<Verdict vendor="valibot" ok={true} detail={JSON.stringify(input)} />,
);
},
);+ 3 supporting declarations (ArkCoupon, ValCoupon, Verdict)
// ── ArkType ──────────────────────────────────────────────────────────────────
const ArkCoupon = type({
code: "string >= 4",
percent: "number | string.numeric.parse",
});
// ── Valibot — same slot, same Ctx, no change to commands.ts ───────────────────
const ValCoupon = v.object({
code: v.pipe(v.string(), v.minLength(4)),
percent: v.union([v.number(), v.pipe(v.string(), v.transform(Number))]),
});
export const Verdict = (p: {
vendor?: string;
ok?: boolean;
detail?: string;
}) => (
<p
id="schema-verdict"
class="mono"
style="margin:12px 22px 0;font-size:12.5px"
>
{p.vendor ? (
<span style={`color:${p.ok ? "var(--green)" : "var(--red)"}`}>
{p.vendor} → {p.detail}
</span>
) : (
<span style="color:var(--dim3)">
try a code shorter than 4 chars — both refuse, in their own words
</span>
)}
</p>
);StandardSchemaV1commands.ts
export type StandardSchemaV1<Input = unknown, Output = Input> = {
readonly "~standard": {
readonly version: 1;
readonly vendor: string;
readonly validate: (
value: unknown,
) => StandardResult<Output> | Promise<StandardResult<Output>>;
readonly types?:
{ readonly input: Input; readonly output: Output } | undefined;
};
};+ 1 supporting declarations (StandardResult)
// ── validation, via Standard Schema ──────────────────────────────────────────
// Structural, so this file depends on no validator. ArkType, Zod 4, Valibot and
// friends all expose `~standard`; the demo happens to use ArkType.
type StandardResult<T> =
{ value: T; issues?: undefined } | { issues: readonly { message: string }[] };Stale command → 410
An id the running code no longer registers — HTML older than the deploy. 410 is the reload signal.
call sitepage.tsx → stale call
<button
class="btn btn-danger"
data-on:click="@post('/command/todo.removedInV2')"
>
call a deleted command
</button>dispatch's 410 branchcommands.ts → stale
let id: string;
try {
id = decodeURIComponent(u.pathname.slice("/command/".length));
} catch {
// `POST /command/%` — decodeURIComponent throws URIError, which would
// otherwise escape dispatch entirely and 500.
return new Response("bad command id", { status: 400 });
}
const fn = byId.get(id);
// Unknown id means HTML older than the running code. 410 is the signal for
// "reload me", which a datastar-fetch error handler can act on.
if (!fn) return new Response(`stale command: ${id}`, { status: 410 });Streaming — one response, many patches
SSE.stream() returns the Response as soon as the stream is constructed, and the handler runs inside it — so each send() writes into a body the browser already has, and every step renders as it lands. This is one request: the log records a single entry with five patches, not five requests.
- ·reading 1,248 rows
- ·validating against schema
- ·writing to store
- ·reindexing
- ·done
call sitepage.tsx → stream call
<button
class="btn"
data-on:click={cmd(runImport)}
data-attr:disabled="$_streaming"
{...{ "data-indicator:_streaming": true }}
>
run import
</button>runImportstream.commands.tsx
import { defineCommand } from "./commands";
import { log } from "./trace";export const runImport = defineCommand("demo.stream", async ({ patch }) => {
for (let i = 0; i < STEPS.length; i++) {
// Each send() writes into the already-open response.
patch.elements(<Progress at={i + 1} />);
await Bun.sleep(420);
}
log(`streamed ${STEPS.length} patches down one open response`, {
payload: { steps: STEPS, patches: STEPS.length },
});
});+ 2 supporting declarations (STEPS, Progress)
const STEPS = [
"reading 1,248 rows",
"validating against schema",
"writing to store",
"reindexing",
"done",
];
/** `at` is how many steps have started: 0 idle, STEPS.length complete. */
export const Progress = (p: { at: number }) => {
const pct = Math.round((p.at / STEPS.length) * 100);
const done = p.at >= STEPS.length;
return (
<div id="stream-out" class="stream">
<div class="bar">
<span class={done ? "done" : ""} style={`width:${pct}%`}></span>
</div>
<ol class="steps">
{STEPS.map((s, i) => {
const state =
done || i < p.at - 1 ? "ok" : i === p.at - 1 ? "now" : "";
return (
<li class={state}>
<span class="mark">
{state === "ok" ? "✓" : state === "now" ? "▸" : "·"}
</span>
{s}
</li>
);
})}
</ol>
</div>
);
};The trace — where the right-hand panel comes from
Nothing in the panel is hand-logged. dispatch times every request, records what the boundary accepted, and wraps the handler's patch in a proxy that captures each call — so the panel shows what a handler actually sent back, not what it meant to. Handlers add colour with ctx.log(), which writes nothing anywhere by itself: it appends to this request's trace, and setRecorder decides where the lines go. Here they become rows; in your app they might be stdout or OpenTelemetry.
level is a union — input, patch, refusal, info — not a string, so a typo'd level is a compile error rather than a category nothing filters on. dispatch infers one per request; log() overrides it, which is how the ownership refusal in 04 turns red.
incrementbasics.commands.tsx
import { defineCommand } from "./commands";
import { log } from "./trace";
import { type } from "arktype";export const increment = defineCommand(
"demo.increment",
{ input: type({ "count?": "number" }) },
({ input, signals, patch }) => {
const next = (input.count ?? 0) + 1;
// `signals` is everything the client sent; `input` is the validated slice.
log(`whole store rode along — ${Object.keys(signals).length} signals`, {
payload: { input, signalKeys: Object.keys(signals) },
});
patch.signals({ count: next });
},
);setRecorderserver.ts → recorder
// snip not found: server.ts#recordergroup() — commands in, one read model out
Three handlers, none of which patches anything. group("cart", …) declares the id namespace, the $cart.* signal scope and the one view that re-renders after a command succeeds — so a handler is a command and nothing else. That is the CQRS split with the read side declared once instead of repeated as a trailing patch on every handler.
empty — add a line
@post('/command/cart.add', {filterSignals: {include: /^cart\./}})The patch runs inside the SSE stream callback, after the handler resolves — the only moment it can, since the stream closes as soon as that callback returns. On the no-JS path there is no patch at all: the 303 and the full page re-render already say the same thing. Streaming handlers opt out with { autoPatch: false }.
the groupcart.commands.tsx → cart group
const cart = group("cart", () => <CartView />);
export const addLine = cart.defineCommand(
"add",
{ input: type({ item: "string > 0" }) },
({ input }) => {
store().addToCart(input.item);
log(`command: add "${input.item}" — the view re-renders itself`);
},
);
export const bump = cart.defineCommand(
"bump",
{ args: type({ id: "number", by: "-1 | 1" }) },
({ args }) => {
store().bumpCart(args.id, args.by);
log(`command: qty ${args.by > 0 ? "+" : ""}${args.by} on line ${args.id}`);
},
);
export const clear = cart.defineCommand("clear", () => {
store().clearCart();
log("command: clear");
});CartViewcart.commands.tsx
import { cmd, group } from "./commands";
import { log } from "./trace";
import { store } from "./store";
import { type } from "arktype";export const CartView = () => {
const lines = store().cart();
const total = lines.reduce((n, l) => n + l.qty, 0);
return (
<div id="cart" class="cart">
{lines.length === 0 ? (
<p class="note" style="margin:0">
empty — add a line
</p>
) : null}
{lines.map((l) => (
<div class="row">
<span class="txt">{l.name}</span>
<span class="owner">×{l.qty}</span>
<button class="ico" data-on:click={cmd(bump, { id: l.id, by: 1 })}>
+
</button>
<button class="ico" data-on:click={cmd(bump, { id: l.id, by: -1 })}>
−
</button>
</div>
))}
<div class="card-ft">
<span>
{lines.length} line{lines.length === 1 ? "" : "s"} · {total} item
{total === 1 ? "" : "s"}
</span>
<span>
patched by <span class="enc">group("cart")</span>
</span>
</div>
</div>
);
};+ 2 supporting declarations (cart, bump)
const cart = group("cart", () => <CartView />);
export const bump = cart.defineCommand(
"bump",
{ args: type({ id: "number", by: "-1 | 1" }) },
({ args }) => {
store().bumpCart(args.id, args.by);
log(`command: qty ${args.by > 0 ? "+" : ""}${args.by} on line ${args.id}`);
},
);Registry
Discovered by globbing *.commands.tsx at boot and importing them. Export and 'is an endpoint' are independent — only defineCommand() registers.
| demo.ping | — |
| demo.increment | input |
| demo.strictIncrement | input |
| cart.add | input:cart |
| cart.bump | args |
| cart.clear | — |
| log.view | — |
| log.clear | — |
| news.subscribe | input:signup |
| demo.stream | — |
| todo.toggle | args |
| todo.remove | args |
| todo.add | input:todo |
| admin.grantRole | args · sealed |
| coupon.ark | input:coupon |
| coupon.valibot | input:coupon |
Reference by function or by string — same endpoint: @post('/command/demo.ping') === @post('/command/demo.ping')
loadCommandscommands.ts
import { pathToFileURL } from "node:url";
import { resolve } from "node:path";// ── discovery ────────────────────────────────────────────────────────────────
// A POST can land on a process that never rendered the page, so every command
// module must be imported at boot on every instance. That is the entire job
// "use server" was doing; a filename does it without reading each source file.
export async function loadCommands(
dir: string,
pattern = "**/*.commands.{ts,tsx}",
) {
const found: string[] = [];
for await (const rel of new Bun.Glob(pattern).scan({ cwd: dir })) {
await import(pathToFileURL(resolve(dir, rel)).href);
found.push(rel);
}
return found.sort();
}summarizetodos.commands.tsx
import { store } from "./store";/**
* Exported, used by the view, reachable from a test — and NOT an endpoint, because
* only defineCommand() registers. Under a "use server" directive this export would be a
* public unauthenticated route.
*/
export const summarize = () =>
`${store().doneCount()}/${store().todos().length} done`;