Five-Minute Tour
I want to show you Adama's core ideas as fast as possible. We'll start with a counter — the "hello world" of stateful systems — and layer on privacy, per-user data, and real-time sync. All in a few lines of code.
A Simple Counter
The simplest possible Adama document:
// A public counter visible to all connected clients
public int count = 0;
One line. That gives you:
- A document with a field called
count - Initialized to
0 - The
publicmodifier means anyone who connects can see it
The backend data is just JSON:
{"count": 0}
Every connected client sees this same data, updated in real-time whenever it changes. No WebSocket setup, no pub/sub wiring. It just works.
Adding Connections
A document nobody can connect to isn't very useful. Let's fix that:
public int count = 0;
// Allow anyone to connect
@connected {
return true;
}
The @connected event fires whenever someone tries to connect to the document. Returning true lets them in. You could add logic here — check an allowlist, limit concurrent connections, whatever you need.
Accepting Messages
Now we need a way to actually change the counter. In Adama, state changes happen through messages:
public int count = 0;
@connected {
return true;
}
// Define a message type
message Increment { }
// Create a channel that handles increment messages
channel increment(Increment msg) {
count++;
}
Two new ideas:
-
Messages — Typed structures that clients send to the document.
Incrementis empty because all we need is the signal "add one." No payload required. -
Channels — Named endpoints that receive messages and run code. The
incrementchannel takes anIncrementmessage and bumps the counter.
When a client sends an Increment message to the increment channel, three things happen:
- The
count++runs on the server - The new value is persisted automatically
- All connected clients get the update
That's it. No manual broadcast, no database write, no cache invalidation.
Adding Privacy
What if some data should be hidden from clients? Easy:
public int count = 0;
private int internal_counter = 0;
@connected {
return true;
}
message Increment { }
channel increment(Increment msg) {
count++;
internal_counter++;
}
The private modifier hides internal_counter from everyone. The document on the server contains both fields:
{"count": 5, "internal_counter": 5}
But clients only ever see:
{"count": 5}
Privacy is enforced at the document level — there's no API trick to sneak around it. It's baked into the runtime.
Viewer-Specific Data
Privacy gets more interesting when different users should see different things. Consider tracking how many times each user has incremented:
public int count = 0;
// Track per-user increment counts
table<UserStats> _stats;
record UserStats {
public principal who;
private int increments;
}
@connected {
// Create a stats record for new users
if ((iterate _stats where who == @who).size() == 0) {
_stats <- {who: @who, increments: 0};
}
return true;
}
message Increment { }
channel increment(Increment msg) {
count++;
// Update this user's stats
(iterate _stats where who == @who).increments++;
}
// Each user sees only their own increment count
bubble my_increments = (iterate _stats where who == @who)[0].increments;
A few new things here:
- Tables — Collections of records. Always private by default.
- Records — Structured types for organizing related data.
- principal — A type representing a connected user's identity.
- @who — The user who sent the current message or is viewing the document.
- bubble — A formula computed per-viewer; each user sees their own value.
The bubble named my_increments uses @who to filter the stats table. User A sees their increment count; User B sees theirs. Neither can see the other's data. The document does the filtering for you — there's no client-side code deciding what to show.
Real-Time in Action
Everything we've covered works together:
public int count = 0;
public formula last_updated = Time.datetime();
@connected {
return true;
}
message Increment { }
channel increment(Increment msg) {
count++;
}
The formula keyword creates a computed value that updates automatically. When count changes, last_updated recomputes, and all clients receive both updates in a single delta.
This is Adama's reactive model at its core:
- Client sends a message
- Server runs the channel code
- All changed values (including formulas) are detected
- Minimal deltas go to each connected client
- Privacy rules filter what each client sees
No WebSocket code. No state synchronization logic. No cache invalidation. The language handles it.
What We Covered
| Concept | Purpose |
|---|---|
public/private |
Control data visibility |
@connected |
Handle connection requests |
message |
Define client-to-server communication |
channel |
Execute code when messages arrive |
table + record |
Store structured collections |
principal |
Represent user identity |
bubble |
Compute viewer-specific data |
formula |
Reactive computed values |
These are the building blocks. They combine in ways that let you build real-time applications with surprisingly little code.
Ready to build something real? Continue to First Application where we'll put together a complete todo list with user authentication, privacy controls, and deployment.