Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions versioned_docs/version-4.6/developers/real-time/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Real-Time
---

# Real-Time

Modern applications demand experiences that update instantly; collaboration apps that feel alive, IoT dashboards that react the moment sensors change, and user interfaces that always show the freshest data without reloads. Harper makes this possible by embedding real-time communication directly into the database layer. Instead of bolting on a separate broker or managing multiple systems, you get structured real-time data and messaging as a first-class capability.

Harper real-time is built around database tables. Any declared table can double as a messaging topic, which means you don’t just publish and subscribe, you also persist, query, and synchronize that data across a distributed cluster. This is where Harper is different from a generic pub/sub hub: it treats data as structured records, not raw messages, and it speaks standard protocols so you can connect from any environment.

You can get started with real-time in Harper in a single step by adding a table to your schema:

```graphql
type Dog @table @export
```

From here, clients can subscribe to this topic, publish structured messages, and receive updates in real-time. Content negotiation is built in: one client can publish JSON, while another consumes CBOR or MessagePack. The database handles translation seamlessly.

Harper supports several real-time protocols so you can pick the one that best fits your application architecture:

- [MQTT](./real-time/mqtt) for IoT and event-driven systems, with tight integration to database records.
- [WebSockets](./real-time/websockets) for full-duplex connections and interactive applications.
- [Server Sent Events (SSE)](./real-time/sse) for lightweight one-way streaming to the browser.

Each protocol page in this section gives you background on the protocol itself, shows how Harper implements it, and walks you through how to use it in your apps.

👉 Whether you’re building dashboards, chat apps, or IoT systems, Harper gives you real-time capabilities without extra infrastructure; just connect and start streaming.
79 changes: 79 additions & 0 deletions versioned_docs/version-4.6/developers/real-time/mqtt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: MQTT
---

# MQTT

MQTT is widely used for lightweight, event-driven communication, especially when you need devices and apps to stay updated in real time. In Harper, MQTT is integrated directly into the database, so your topics map to real records instead of being just abstract channels. This makes it possible to persist state, stream updates, and control how data flows across distributed servers, all while using a standard protocol.

To make this concrete, let’s use a `Dog` table for a pet adoption app. Every record in this table automatically becomes an MQTT topic. That means if you store a dog with the ID `123`, you can subscribe to `dog/123` and immediately receive updates whenever that record changes.

```graphql
type Dog @table @export {
id: ID @id
name: String
breed: String
}
```

## Subscribe to a record

When a client subscribes to `dog/123`, Harper first delivers the current record value as a retained message, then streams every update or deletion that follows. This ensures your app always has the latest state without needing a separate fetch.

## Publish updates

Publishing to the same topic updates or notifies subscribers depending on whether the retain flag is used.

- Retained messages update the record, replacing its state.
- Non-retained messages leave the record unchanged but notify subscribers.

This gives you control over whether messages represent state or events.

## Wildcards for multiple records

To follow more than one dog at once, you can subscribe with a trailing multi-level wildcard:

```bash
dog/#
```

This streams notifications for every record in the `Dog` table.

## Configuration

MQTT is enabled by default, but you can adjust ports, TLS, and authentication in your `harperdb-config.yaml`:

```yaml
mqtt:
network:
port: 1883
securePort: 8883
webSocket: true
mTLS: false
requireAuthentication: true
```

For more advanced options (like enabling mTLS or customizing ports), see the [Configuration page](../../deployments/configuration).

👉 If you connect over WebSockets, remember to include the sub-protocol: `Sec-WebSocket-Protocol: mqtt`.

## Event hooks

On the server side, you can hook into MQTT lifecycle events to log or react when clients connect, fail authentication, or disconnect:

```javascript
server.mqtt.events.on('connected', (session, socket) => {
console.log('client connected with id', session.clientId);
});
```

## Ordering and delivery

Because Harper is distributed, messages can arrive out of order depending on network paths. Delivery rules are:

- **Non-retained messages** - always delivered, even if delayed or out of order.
- **Retained messages** - only the latest state is kept across all nodes.

This lets you handle use cases like chat (where every message matters) and IoT telemetry (where the latest reading matters most).

👉 Next: explore [WebSockets with Harper](./websockets) for full-duplex connections directly in the browser.
98 changes: 98 additions & 0 deletions versioned_docs/version-4.6/developers/real-time/sse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Server-Sent Events (SSE)
---

# Server-Sent Events (SSE)

There are times when you don’t need a two-way connection. The client never needs to send data back, it only needs to stay in sync with what the server knows. For example, a dog adoption dashboard might show the availability of each dog, and all it has to do is keep that list updated in real time.

SSE (Server-Sent Events) is perfect for this. It gives you a persistent one-way stream from the server to the client, using simple HTTP. Harper makes this stream available on any resource path.

## Opening a connection to a record

On the client, you create an `EventSource` that points to the dog you want to follow.

```javascript
const es = new EventSource('https://server/dog/341', { withCredentials: true });
```

When the connection opens, it stays alive until you close it. Messages arrive as events you can listen for.

```javascript
es.onmessage = (event) => {
const data = JSON.parse(event.data);
renderDogProfile(data); // update the profile in your UI
};
```

## Handling errors and reconnects

SSE connections will automatically try to reconnect if they drop, but you should still handle errors gracefully.

```javascript
es.onerror = (err) => {
console.error('sse error', err);
showConnectionWarning();
};
```

When the user leaves the profile page, close the connection cleanly.

```javascript
es.close();
```

## Adding custom events from the server

By default, connecting to `/dog/341` streams record updates. But Harper also lets you enrich that stream by defining a `connect()` method on the resource. This lets you send your own events alongside updates.

```javascript
export class DogStream extends Resource {
connect() {
const outgoing = super.connect();

// send a friendly reminder every 30 seconds
const reminder = setInterval(() => {
outgoing.send({ dogId: '341', notice: 'still looking for a home' });
}, 30000);

outgoing.on('close', () => {
clearInterval(reminder);
});

return outgoing;
}
}
```

Now the client receives both the automatic record updates and the server’s notices.

## Reading server messages on the client

On the client, branch on fields in the incoming event and update different parts of the UI.

```javascript
es.onmessage = (event) => {
const msg = JSON.parse(event.data);

if (msg.notice) {
showNotice(msg.notice);
return;
}

renderDogProfile(msg); // treat as a record update
};
```

## Why choose SSE for your app

- Lightweight and browser-native: just new EventSource().
- Automatic reconnects built into the browser.
- Ideal for dashboards, feeds, and one-way updates.
- Less complex than WebSockets when clients don’t need to send data.

At this point, your adoption dashboard can show dog profiles that stay fresh automatically, display occasional server notices, and handle connection lifecycle gracefully, all without writing a line of custom client transport code.

When you want to adjust TLS, authentication, or ports, do that in your Harper configuration file. See the [Configuration page](../../deployments/configuration) for details.

👉 If you later decide the client should also send messages back (like user comments or notes), [WebSockets](./websockets) give you that two-way channel.
136 changes: 136 additions & 0 deletions versioned_docs/version-4.6/developers/real-time/websockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
title: WebSockets
---

# WebSockets

There are times when you want more than just notifications about record changes. You need a live connection that stays open, one that lets updates stream from the server and messages flow back from the client. That’s exactly what WebSockets provide, and Harper makes them even more powerful by tying them directly to resources.

Let's imagine you have a dog profile screen open and you want it to stay fresh while the user looks at it. If the record for `dog/341` changes, the page should reflect it right away. A WebSocket gives you a live path from server to client and back again, so the page can breathe in real time.

You begin on the client by opening a connection to the resource path for that record.

```javascript
const ws = new WebSocket('wss://server/dog/341');
```

As soon as it connects, confirm it is alive so you can update any loading indicator.

```javascript
ws.onopen = () => {
console.log('connected to dog/341');
};
```

Now listen for messages. Each message is JSON that represents either a record update or an event you choose to send from the server.

```javascript
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
renderDogProfile(data);
};
```

If the connection has trouble, surface it. Errors are rare but silence is painful during development.

```javascript
ws.onerror = (err) => {
console.error('websocket error', err);
};
```

When the user navigates away, close the socket so the server can clean up.

```javascript
ws.close(1000, 'leaving dog profile'); // normal closure
```

That already gives you a live profile. Any change to `dog/341` flows to the page without reloads or polling. Sometimes, though, you want more than passive updates. You want the page to send signals to the server, and you want the server to push its own messages alongside record changes.

Send a small message from the client. Keep it simple and structured.

```javascript
ws.send(JSON.stringify({ dogId: '341', note: 'good with kids' }));
```

On the server, define how the resource streams data back to connected clients. You can take full control by implementing `connect(incomingMessages)` on a resource. The method returns a stream. Yield values to push them to the client.

```javascript
export class DogNotes extends Resource {
async *connect(incomingMessages) {
for await (const message of incomingMessages) {
// relay user notes for this dog
yield { dogId: message.dogId, note: message.note };
}
}
}
```

This turns the profile into a small collaborative space. Anyone connected to `dog/341` can post a note and everyone sees it arrive in real time.

Sometimes you want to keep the default behavior for record updates and also add your own messages. Call `super.connect()` to get a convenient stream object that you can send on, and that fires a close event when the client leaves.

```javascript
export class DogStatus extends Resource {
connect(incomingMessages) {
const outgoing = super.connect();
return outgoing;
}
}
```

Add a very small server message so the client can show connection health. A steady pulse makes debugging simple and gives product a tiny hook for a status badge.

```javascript
const timer = setInterval(() => {
outgoing.send({ dogId: '341', status: 'connection active' });
}, 1000);
```

Let the server react to what the client sends. Here we echo notes back through the stream so everyone connected stays in sync.

```javascript
incomingMessages.on('data', (message) => {
outgoing.send({ dogId: message.dogId, note: message.note });
});
```

Clean up properly when the socket closes. No timers left behind.

```javascript
outgoing.on('close', () => {
clearInterval(timer);
});
```

Return the stream and you are done.

```javascript
return outgoing;
```

Back on the client, keep the message handler tidy. Branch on simple fields and update the page.

```javascript
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);

if (msg.note) {
appendNote(msg.dogId, msg.note);
return;
}

if (msg.status) {
setConnectionBadge(msg.status);
return;
}

renderDogProfile(msg); // treat as a record update
};
```

At this point the dog profile page is alive. It receives record updates for `dog/341`, it lets users post live notes, and it shows a gentle status pulse so the UI can reflect connection health. All of this runs over one WebSocket to one resource path.

If you need to tune transport details like TLS, ports, or authentication, keep the page focused and link out rather than dumping settings here. See the [Configuration page](../../deployments/configuration) when you are ready to adjust instance settings.

👉 Next up, if you prefer a one way stream where the server talks and the client just listens, move to [Server Sent Events (SSE)](./sse).
Loading