# Realtime

> Realtime in Osy# is one construct: a topic. A topic is a declared, addressable destination — publishing to one delivers the value to every open page entitled to it, with no polling and no re-query. Its parameters are its address, its Candidates rule says who may join, and a topic marked Presence tracks who is currently there, derived from the connections themselves.

<!-- id: realtime-index · area: realtime · stability: stable · html: https://osysharp.com/reference/realtime/index/ -->

## Summary        {#summary}

Realtime is **one construct**: a [topic](https://osysharp.com/reference/realtime/topic/). Everything else — rooms, threads, direct messages, typing
indicators, read receipts — is ordinary app modelling on top of it.

```osy title="the smallest complete topic" test app=realtime-smallest
[Principal] entity User {
  [MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}

class Tick { public int Count; }

topic Heartbeat() {
  Candidates = _ => true;        // who may subscribe. REQUIRED — silence is a compile error
  Carries    = Tick;             // what each message holds
}

void Beat(int n) {
  Heartbeat.Publish(new Tick { Count = n });
}
```

## Description    {#description}

### What a topic is for   {#purpose}

A page that must reflect something happening elsewhere has three options, and only one of them scales:

| approach | what it costs |
|---|---|
| poll | every page re-asks on a timer, whether or not anything changed |
| signal, then re-read | one message tells the page *something* changed and it re-runs its whole query |
| **a topic** | the message carries the value, so the page receives exactly what changed |

The middle option is what a plain live query does, and it is correct but expensive: a signal carrying no value can
only be answered by re-asking the whole question. Measured, a room of ten people a hundred messages deep moves about
a thousand rows to deliver one "ok" — every one of which was already on every screen. The same message on a
value-carrying topic moves ten.

### Security is declared, once   {#security}

A topic's `Candidates` rule decides who may subscribe, and the platform enforces it at the moment of joining. There
is nothing to remember at the call site and nothing an app can bypass, which is the same split entity security
already has: the app owns the policy, the platform owns the enforcement.

Because the address is made of typed parameters rather than a string name, **a topic's name is never its security
boundary** — guessing `RoomFeed` gets you nothing without a rule that admits you at that address.

### What is not part of it   {#not-included}

A topic is transport. The vocabulary of a chat product — channels, threads, DMs, reactions, read receipts,
moderation — is app modelling, and the platform deliberately ships none of it: those are entities and rules an app
declares, and they are better for being the app's own.

## Examples       {#examples}

See [topic](https://osysharp.com/reference/realtime/topic/) for compiled examples: a room feed with a membership rule, an explicitly public topic, a
broadcast topic with a separate publish rule, a webhook publishing with no row to carry it, and presence.

## See also       {#see-also}
- [topic](https://osysharp.com/reference/realtime/topic/) — the construct, in full
- [Listen](https://osysharp.com/reference/realtime/listen/) — the subscribe half: what a page receives
- [Here, Announce](https://osysharp.com/reference/realtime/presence/) — who is here, and what they are doing
- [security { }](https://osysharp.com/reference/security/entity-security/) — the `allow` rules a `Candidates` predicate reads
- [component](https://osysharp.com/reference/ui/component/) — where a subscription is consumed
