Actions
Actions handle form submissions. They are plain HTML forms posting to server functions in +page.server.ts so they work without JavaScript, and the framework intercepts the submit to fetch and re-render when it is available.
// routes/posts/new/+page.server.ts
import { ok, seeOther } from "@torpor/build";
import { type PageServerEndPoint } from "@torpor/build";
import { createPost } from "@/lib/db";
export default {
actions: {
default: async ({ form }) => {
const { title } = await form();
await createPost({ title });
return seeOther("/posts");
},
},
} satisfies PageServerEndPoint;
// views/NewPost.torp
export default function NewPost() {
@render {
<form method="POST">
<input name="title" />
<button>Create</button>
</form>
}
}
Named actions
A form without an action attribute calls the default action. Point the action at a query value to call a named one:
<form method="POST" action="?/like">
<button>Like</button>
</form>
actions: {
like: async ({ params }) => {
await addLike(params.id);
},
},
Validation
Declare a schema for an action (any Standard Schema library like Zod, Valibot, or ArkType works) and its form values are validated and typed before the action runs. A form that fails validation gets a 422 response with the schema's issues, without calling your action:
const schema = {
default: z.object({ title: z.string().min(1) }),
};
export default {
schema,
actions: {
default: async ({ form }) => {
const { title } = await form(); // typed as string
await createPost({ title });
return ok({ saved: true });
},
},
} satisfies PageServerEndPoint<"/posts/new", Record<string, any>, typeof schema>;
Action results
When an action returns a response with a JSON body — from ok({ ... }), unprocessable({ ... }), etc — the page is re-rendered and the body becomes $props.form (also available as $page.form from @torpor/build/state):
export default function NewPost($props: { form?: { saved?: boolean } }) {
@render {
@if ($props.form?.saved) {
<p>Saved!</p>
}
<form method="POST">
<input name="title" />
<button>Create</button>
</form>
}
}
Use unprocessable to return validation errors with their field types, and seeOther to redirect after a successful submission.