Input schemas
input takes any Standard Schema v1: an object with a ~standard member whose validate(value) returns { value } or { issues } (synchronously or as a promise).
input takes any Standard Schema v1: an object with a ~standard member whose
validate(value) returns { value } or { issues } (synchronously or as a promise). Zod, Valibot and ArkType
implement it, and so does the small schema() helper above. The handler's input type is the schema's output
type; the callable's argument type is its input type. serverFn throws a TypeError at definition time when input
is not a Standard Schema. Without input, the handler receives the untrusted value as unknown.
The argument is optional exactly when undefined is a valid input: a function without input, or with a schema of
an optional value, is called as stats(); a schema that does not accept undefined makes the argument required,
and getNote() is a type error. The browser stub has the same rule, so a call that compiles in a loader compiles
in an event handler.
import { getNote, stats } from "./notes";
/** `stats` has no input schema: no argument. `getNote` has one that needs a value: the argument is required. */
export async function overview(): Promise<[number, string]> {
const total = await stats();
const first = await getNote({ id: "1" });
return [total, first.title];
}
A schema that reports issues makes the handler answer 400 and the function does not run. The thrown
ValidationError is an HttpError with status 400 and the message
Invalid input; its issues are { message, path } pairs. What leaves the server is bounded by
ISSUE_LIMITS: at most 20 issues, 200 characters per message and 16
path segments. Path segments may be strings or numbers (a { key } segment is reduced to its key, a symbol to its
description). A schema that throws instead of reporting issues is an unexpected error and answers 500.
import { serverFn } from "@shadow-js/dusk/server";
import { schema } from "../schema";
/** A schema that reports far more than a client can use. */
const noisy = schema<never>(() => ({
issues: Array.from({ length: 30 }, () => ({
message: "m".repeat(300),
path: Array.from({ length: 20 }, (_, index) => index),
})),
}));
export const refuse = serverFn({ input: noisy }, async (): Promise<string> => "unreachable");
The client rebuilds the error as an HttpError from @shadow-js/dusk with status 400 and
data: { issues }, so a form can show the messages next to its fields.