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 public modifier 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:

  1. Messages — Typed structures that clients send to the document. Increment is empty because all we need is the signal "add one." No payload required.

  2. Channels — Named endpoints that receive messages and run code. The increment channel takes an Increment message and bumps the counter.

When a client sends an Increment message to the increment channel, three things happen:

  1. The count++ runs on the server
  2. The new value is persisted automatically
  3. 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:

  1. Client sends a message
  2. Server runs the channel code
  3. All changed values (including formulas) are detected
  4. Minimal deltas go to each connected client
  5. 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.

Previous Installation
Next First App