norns

Page wrappers

page.load and page.actions wrap +page.server.c exports with container resolution, form parsing and valibot validation.

page is auto-imported in server files. It mirrors route() with two differences that match SvelteKit's form conventions: load returns its result as plain data, and actions return fail(400, ...) on validation errors instead of throwing.

import { notes } from '$lib/norns/notes/server/public'
import { createNoteSchema } from '$lib/norns/notes/shared/schema'

export load := page.load
	handler: async ({ container }) =>
		notes: await notes(container).list()

export actions := page.actions
	create:
		input: createNoteSchema
		run: async ({ input, container }) =>
			id := await notes(container).create input
			throw redirect 303, `/notes/${id}`

#page.load({ handler })

The handler receives:

Field Value
container the request-scoped container (event.locals.container)
event the raw SvelteKit ServerLoadEvent
params, url shortcuts for event.params and event.url
user event.locals.user, whatever your auth handle put there

Whatever the handler returns is the page's data. Throw error(404, ...) or redirect(...) as in plain SvelteKit.

#page.actions({ name: { input?, run } })

Each action is { input?, run }:

  • When input is present the form body is read with request.formData(), flattened with Object.fromEntries, and validated. On failure the action returns fail(400, { errors, values }): errors is the issue list, values is the raw form data echoed back so the page can re-render it.
  • run receives { input, container, event, user }. input is the validated value (or undefined when there is no schema).

A route with a dynamic parameter reads it from the event:

export actions := page.actions
	update:
		input: updateNoteSchema
		run: async ({ input, container, event }) =>
			id := Number event.params.id
			await notes(container).update id, input
			{ saved: true }

	delete:
		run: async ({ container, event }) =>
			await notes(container).remove Number(event.params.id)
			throw redirect 303, '/notes'

#Rendering errors

The fail(400, { errors, values }) shape is what norns-ui's <Form form={form}> reads. Each <Field name="..."> inside looks up its own message from errors by path, and values lets inputs keep what the user typed:

Form(action="?/create" form!="{form}")
	Field(label="Title" name="title" required)
		Input(name="title" value!="{form?.values?.title ?? ''}" required)
	Btn(type="submit" variant="primary") Create

Note

Actions only see form-encoded bodies. A client that posts JSON must call a +server.c endpoint wrapped in route(), not a form action.