Services
A service is a named singleton declared in main.rat with its own state and methods. Pages call it as auth.login(...) and every call hits the same underlying frame — perfect for authentication, shopping carts, anything you want a single instance of for the duration of the app.
Declare a service
main.rat
> service [auth] defines the namespace. The block has state lines (user: null) and method definitions, each of which can read and write the state via <<. A method returns with << on its own.
> service [auth]
user: null
token: null
login[u, p]
user << u
token << 'tok-' + p
<< true
logout[]
user << null
token << null
is_authed[] >> token is not null Call it from a page
auth.login(...) reads like any other call
Any page (or pup) can call the service methods. Reads of the service's state (auth.user) work too — the page participates in the reactive graph against the service's frame, so a successful login causes every [auth.user] span in the app to repaint.
> page
name: ''
pw: ''
who >> auth.user
<p> signed in as: [who]
<input value[name] on_input[name << event.target.value]>
<input value[pw] on_input[pw << event.target.value]>
<button on_click[auth.login(name, pw)]> sign in
<button on_click[auth.logout()]> sign out Why services over plain functions
State that survives across pages
A top-level > name[] function is stateless — it returns a value and forgets. A service carries state, so auth.user stays around for as long as the app runs. The same mechanism backs database handles (main_db.user.add(...)) and Tail-style namespaces — see the unified dispatch doc for the deeper story.
# stateless: forgets between calls
> add[a, b] >> a + b
# stateful: the singleton remembers
> service [counter]
value: 0
bump[] >> value << value + 1 Live: a counter service
demo_counter is declared in main.rat
Two buttons calling the same service. The displayed value is read from demo_counter.clicks, so the span updates whenever any caller bumps the service — try refreshing the page and the count survives the navigation because the singleton lives in the server frame, not in this page.
# main.rat (already declared)
> service [demo_counter]
clicks: 0
bump[]
clicks << clicks + 1
reset[]
clicks << 0
# this page
<p> clicks: [demo_counter.clicks]
<button on_click[demo_counter.bump()]> +1
<button on_click[demo_counter.reset()]> reset clicks: 0
That wraps the basics. Head back to the docs index for the curated example list, or dive into the about page for context on how the docs site itself is built.