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.