# Getting Started with Statum ## What is Statum Statum is a stateful web application framework for Vala, built on top of [Astralis](https://github.com/search/astralis) (HTTP server), [Invercargill](https://github.com/search/invercargill) (data structures / JSON), and [Inversion](https://github.com/search/inversion) (dependency injection). It provides: - **Server-rendered HTML pages** composed from templates and fragments at build time (`statum-mkpstm`), precompressed with gzip/zstd/brotli and served with encoding negotiation and ETags. - **Client-carried state** — signed and (for private data) encrypted snapshots that the browser holds and sends back, so the server is stateless between requests (no session store, no database for ephemeral state). - **Entrypoint hydration** — after a page loads, a lightweight JS client fetches directives that set the initial slot data and bind the DOM. - **GUID actions** — POST/GET handlers auto-mapped to `/_statum/action/{guid}`, invoked from the client via `stm-action` attributes, carrying strongly-typed private data. - **Realtime updates** — a SharedWorker-backed SSE channel pushes slot changes to subscribed tabs. ### Architecture at a glance ``` Browser Server ─────── ────── GET / ──────────────────────► StatumPage (precompressed HTML) page loads, statum.js boots GET /_statum/entrypoint ────► StatumEntrypoint → directives (set/clear/...) client applies directives client binds stm-* attributes POST /_statum/action/{guid} ► StatumAction → directives (with X-Statum-Slot headers) (mutate state, push to channels) ``` --- ## Quick Start Examples ### 1. A page with an entrypoint **`home.html`** — the page template, composed with a layout: ```html / main

Counter: 0

``` **`main.html`** — the layout template: ```html main My App

My App

``` **`HomeEntrypoint.vala`** — hydrates the page: ```vala public class CounterPublic : Object { public int value { get; set; } public Model.ActionDto bump { get; set; } } public class HomeEntrypoint : StatumEntrypoint { public override async DirectiveBuilder handle() throws GLib.Error { var counter = new CounterPublic(); counter.value = 0; counter.bump = action_registry.author_private(new BumpPrivate()); return directives().set_typed("counter", Scope.PAGE, counter); } } ``` **`Main.vala`** — wires it all up: ```vala void main(string[] args) { var app = new WebApplication(8080); app.add_module(); var statum = app.configure_with(); statum.add_page(); // route from statum.action(); // GUID auto-assigned app.run(); } ``` ### 2. A guarded page ```html /dashboard main ``` The guard redirects unauthenticated visitors to `/` before the entrypoint loads. --- ## Server-side Security ### Authorisation guards on `/_statum/*` endpoints (SA-6) The framework endpoints — `GET /_statum/entrypoint`, `GET /_statum/channel` and `PATCH /_statum/channel/{id}` — are open by default: the framework cannot know which slot data is public. Applications gate them through the configurator: ```vala var statum = application.configure_with(); // Return null to allow, or an HttpResult (401 / login redirect) to deny. statum.guard_entrypoint(ctx => auth.require_login(ctx)); statum.guard_channel(ctx => auth.require_login(ctx)); statum.guard_channel_subscriptions(ctx => auth.require_login(ctx)); ``` Spry applications should install Spry guards here (see the Spry README's session-guard helpers). ### Channel resource bounds (SA-15) Concurrent channel (SSE) connections are capped globally (`ChannelEndpoint.MAX_TOTAL_CONNECTIONS`, 256) and per remote address (`ChannelEndpoint.MAX_CONNECTIONS_PER_IP`, 16); each channel holds at most `MAX_SUBSCRIPTIONS_PER_CHANNEL` (64) subscriptions. Beyond the caps the channel endpoint answers `503`. ### Frame expiry on restore (SA-4) `StateService.restore_slot` rejects (and invalidates) frames past their snapshot's `invalid_after`, and keeps the snapshot's `as_at` as the slot's `last_touched` — so replayed frames cannot pin slots forever and the cleanup sweep still expires restored slots. `verify_frame` also asserts the frame's `signer` matches the configured signing key (single-signer contract, SA-10). ### Key generation (SA-12) `statum-genkeys` never prints secret key halves to stdout. Use: ```bash statum-genkeys --out web-config.json ``` which writes the full config at mode 0600 and prints only the public halves. ### Attribute expressions are trusted authoring (SA-18) `stm-*` attribute values are compiled with `new Function('scope', 'with (scope) { … }')` in `statum.js`. That makes them **trusted template authoring**, on exactly the same footing as server-side page markup: never build an expression string from user-supplied data (request bodies, slot values, URL parameters) — interpolate user data only as *values* the expression reads (`stm-text` → `textContent`, `stm-html` → the escaped HTML path, `stm-markdown` → the CommonMark renderer). Today no shipped component binds user data into expression *source*; keeping it that way is a page author's contract, not a runtime guarantee. --- ## HTML Templates Statum pages are HTML files processed at build time by `statum-mkpstm`. ### Build-time directives (`pstm-*`) All `pstm-*` elements **must appear inside ``** (except `pstm-content` and `pstm-fragment`, which live in ``). | Directive | Where | Purpose | |---|---|---| | `/path` | head | The page's route. **Required.** Supports templates: `/cats/{species}`. | | `main` | head | Wraps this page in the named template (by ``). | | `main` | head | Names this file so pages/fragments can reference it. | | `…` | head | Page title, merged up the template chain. | | `` | body (template) | The outlet where the child page's body is inserted. | | `` | body | Inlines a fragment (component) by name. | | `pstm-materialise-as="div"` | `` attr | When this body is composited into a template or fragment, it is wrapped in the named element. | | `pstm-res-src="logo.png"` | any attr | Rewrites to `src="/_statum/resource/logo.png"`. Also `pstm-res-href`, etc. | ### Head merging When a page is composed into a template (or a fragment is inlined), the child's `` children are **merged** into the parent's ``. This lets pages, templates, and fragments each declare their own `