| name | harper-realtime |
| description | adding or troubleshooting… |
Harper Realtime
Overview
Harper exposes live data through the same Resource model used for REST and
GraphQL. Exported tables/resources can be addressed as MQTT topics and WebSocket
paths, while custom resources can override the messaging handlers when the default
table behavior is not enough.
Use real-time primitives when the product needs pushed state: live feeds, activity
streams, collaborative views, device telemetry, or status updates. Do not replace
these with client polling unless the issue explicitly asks for polling.
Resource and topic model
- A GraphQL type marked
@table @export creates a default exported table resource.
- An exported JavaScript Resource class from
resources.js creates a resource
endpoint with the class name as the path/topic root.
- Resource paths map naturally to subscription targets: collection-level
subscriptions use the resource name, and record-level subscriptions append the
record id or path segment.
- MQTT supports multi-level topics and wildcards. Use
resource/# for a resource
subtree and resource/+/status for one path segment.
- WebSocket subscriptions target the REST resource URL. For example,
ws://localhost:9926/Activity/123 subscribes to the Activity resource record
with id 123.
Keep the URL/topic names stable. If a UI depends on Activity/123, changing the
resource export name is an API break.
Resource methods
The MQTT plugin routes broker messages through Resource methods:
| Method | Use |
|---|
subscribe(target, context) | Authorize and shape subscription reads for a resource or record. |
publish(target, message, context) | Accept or transform messages written to a topic/resource. |
connect(incomingMessages) | Customize WebSocket connection behavior for a resource. |
For table-backed resources, prefer default behavior until the product needs a
custom message shape, authorization check, fan-out, or derived event. When
overriding, preserve built-in table behavior with super.subscribe(...) or
super.publish(...) where that behavior is still wanted.
export class Activity extends tables.Activity {
static async publish(target, message, context) {
const payload = await message;
await super.publish(target, payload, context);
return { ...payload, acceptedAt: Date.now() };
}
}
For WebSocket-only behavior, implement connect(incomingMessages) and return an
async iterable. Use the default super.connect() stream when you only need to
push extra server messages or clean up on disconnect.
Transport choices
| Transport | Best fit | Notes |
|---|
| MQTT | Device streams, backend subscribers, wildcard topics, durable clients. | Plain MQTT defaults to port 1883; MQTTS defaults to 8883. |
| MQTT over WebSocket | Browser or edge clients using MQTT semantics. | Uses the HTTP port, default 9926, with the mqtt WebSocket subprotocol. |
| Resource WebSocket | Browser live views tied to one REST resource path. | Enabled with the rest plugin unless rest.webSocket: false is set. |
| SSE/custom streaming route | One-way browser updates when WebSocket is not appropriate. | Implement as a project route; do not assume SSE automatically subscribes to Resource changes. |
Authentication is usually required for MQTT. When mqtt.requireAuthentication is
false, authorization still applies at the resource/table level, so public
connections must be explicitly allowed by roles or resource logic.
config.yaml wiring
Component-level config.yaml enables the app surface:
rest: true
graphqlSchema:
files: schema.graphql
jsResource:
files: resources.js
The root Harper harper-config.yaml, not the component config.yaml, owns broker
ports and MQTT authentication:
mqtt:
network:
port: 1883
securePort: 8883
webSocket: true
requireAuthentication: true
Keep the rest, graphqlSchema, and jsResource declarations together when a
project has a custom component config.yaml. Harper does not merge custom
component config with defaults. See [[harper-config-yaml]].
Filtering and message shape
- Prefer record-level subscriptions (
Resource/<id>) when the UI watches one
entity; use collection or wildcard subscriptions only when the product needs a
feed.
- Keep published messages typed and small. Send the changed record or a compact
event envelope, not a full page payload.
- Include stable identifiers in custom events: resource name, id, operation, and
timestamp are usually enough.
- Treat inaccessible or unauthenticated subscribe attempts as authorization
failures, not empty streams.
- Do not disable the audit log for tables that need real-time messages or
replication; Harper uses it for live subscriptions.
Fabric and replication
For clustered/Fabric deployments, real-time behavior must match the deploy target:
- Verify against the same environment that users connect to, not only a local
single-node process.
- Use
replicated=true when deploying a component that needs to run across Fabric
nodes. See [[harper-build-and-deploy]].
- Keep event handlers idempotent. A subscriber may reconnect and replay from the
latest known state.
- If a bug appears only across nodes, capture the target URL, topic/path, node
count when known, and the exact publish/subscribe commands in the evidence.
Local verification
-
Build generated Harper assets from source:
bun run build
-
Start the component:
harper dev harper-app
-
In another shell, subscribe to a topic or WebSocket path:
npx mqtt sub -h localhost -p 1883 -u HDB_ADMIN -P "$HARPER_PASSWORD" -t 'Activity/#' -v
npx wscat -c ws://localhost:9926/Activity/123
-
Publish or mutate the watched record:
npx mqtt pub -h localhost -p 1883 -u HDB_ADMIN -P "$HARPER_PASSWORD" -t 'Activity/123' -m '{"status":"live"}'
-
Confirm the subscriber receives the expected message shape and that a normal
REST/GraphQL read returns the same resulting state.
Record the command, topic/path, payload, and observed message in PR evidence. If a
local Harper binary or credentials are unavailable, report that blocker instead of
claiming the subscription was verified.
Project conventions
- Write resource code in TypeScript under
src/; harper-app/resources.js is a
generated artifact. See [[harper-resources]].
- If real-time work changes
config.yaml, update the project Fabric runbook and
re-run the smoke path. See [[harper-config-yaml]].
- Prefer a pushed update over a polling workaround when the issue asks for live
behavior.
- For Lisa plugin edits, change
plugins/src/harper-fabric, run
bun run build:plugins, and commit both source and generated plugin copies.
Sources