Rindle Cloud

Connect your app to Rindle Cloud

Point your API server and browser client at a managed Rindle Cloud app using the fleet, control, and write endpoints from the dashboard.

View as Markdown

You provisioned an app on Rindle Cloud (if not, start with the Cloud quickstart). Now you point your two tiers at it: the API server that holds your app’s authority, and the browser client your users run. Nothing about your app code changes — the dashboard supplies connection coordinates and server-side credentials for the same SDK you’d use self-hosted.

The values on your dashboard

Open your app and find the Connect panel. It surfaces exactly what the two tiers need:

Value Who uses it Goes into
Live querieswss://… the browser createRindleClient({ daemon: { wsUrl, affinity: true } })
Control planehttps://…:8443 the API server (reads) the reads leg — new HttpRindleDaemonClient({ baseUrl })
Write-masterhttps://… the API server (writes) the writes leg of the SplitDaemonClient
Daemon token the API server the Bearer header on the reads leg
Replicator token the API server the Bearer header on the writes leg

Every app is a rindle-replicator write-master plus one or more rindled read-followers, so the API server has two legs: reads to a follower’s Control plane, writes to the write-master. The split is the whole security model: the token never reaches the browser. Only your API server holds it, and it dials the data tier’s bearer-gated private control plane. The browser only ever gets the public, lease-gated WebSocket — it can subscribe, but every query is authorized by your API server first.

The tokens are per-app secrets. The dashboard masks them until you click reveal, and the snippets read it from an environment variable rather than inlining it — put it in your API server’s secret store (RINDLE_DAEMON_TOKEN below), not in source control.

Your API server → the daemon

This is the API server page’s wiring with the baseUrl and the token swapped for your dashboard’s Control plane endpoint and your RINDLE_DAEMON_TOKEN. Everything else — your queries, your shared mutators, your authorizers — is unchanged:

import { HttpRindleDaemonClient } from "@rindle/daemon-client";
import { createRindleApiServer, SplitDaemonClient } from "@rindle/api-server";
import { schema, queries, mutators } from "./app-api.ts";   // your registerQueries + sharedApiMutators (server/api.ts in the quickstart)

// The API server is the only tier that holds the token — it never reaches the browser.
// One topology: reads → the follower (Control plane), writes → the write-master.
const reads = new HttpRindleDaemonClient({
  baseUrl: "https://rindle-ab12cd34.fly.dev:8443",          // ← Control plane (reads), from your dashboard
  headers: { authorization: `Bearer ${process.env.RINDLE_REPLICATOR_TOKEN}` },
});
const daemon = new SplitDaemonClient(
  new HttpRindleDaemonClient({
    baseUrl: process.env.RINDLE_REPLICATOR_URL!,            // ← Write-master (writes), from your dashboard
    headers: { authorization: `Bearer ${process.env.RINDLE_DAEMON_TOKEN}` },
  }),
  reads,
);

export const api = createRindleApiServer({
  daemon, schema, queries, mutators,
  // …plus the same authorizeQuery / authorizeMutation you already had — unchanged.
});
// mount api.routes on your HTTP host (node:http, a Worker, Lambda, Hono, …)

Because @rindle/api-server is transport-agnostic, the API server can run anywhere that speaks HTTPS — a Cloudflare Worker, a Node process, a Lambda. It reaches the follower’s Control plane (reads) and the write-master (writes) over TLS with the bearer token; you don’t run anything inside Rindle Cloud to make those calls.

Your browser client → your API server

The client connects to your API server for query leases and mutations, and opens its live subscription against the dashboard’s Live queries endpoint:

import { createRindleClient } from "@rindle/optimistic";
import { schema, mutators } from "./shared/rindle";

const app = await createRindleClient({
  schema,
  mutators,
  user: () => currentUser(),                          // a mutator's ctx.user
  api: { url: "" },                                   // your own API server (above), same-origin
  daemon: {
    wsUrl: "wss://rindle-ab12cd34.fly.dev",           // ← stable fleet endpoint
    affinity: true,                                    // keep ws + lease on one follower
  },
});

That’s the entire change from local development: the follower’s wsUrl and the API server’s read/write endpoints move from 127.0.0.1 to the endpoints on your dashboard.

What changes from self-hosted

If you followed the self-host docs first, only the connection coordinates differ — your queries, mutators, schema, and React code are identical:

Self-hosted (local) Rindle Cloud
API server reads (RINDLE_DAEMON_URL) http://127.0.0.1:7600 the Control plane endpoint (:8443, TLS)
API server writes (RINDLE_REPLICATOR_URL) http://127.0.0.1:7611 the write-master endpoint
Client daemon.wsUrl ws://127.0.0.1:7601 the Live queries endpoint (wss://)
Daemon token one you set on the pair minted per app, on your dashboard
Replicator token one you set on the master minted per app, on your dashboard
Running the data tier you operate the pair operated for you

Multi-node apps

Every managed app is a rindle-replicator write-master plus one or more separately placed rindled read-followers: reads go to the Control plane / Live queries endpoints, writes to the write-master. Scaling from one follower to a regional fleet keeps the same connection shape:

  • Reads keep using one stable Live queries endpoint. With affinity: true, a follower mints a signed placement ticket; the WebSocket and lease request are then replayed to that same follower. A sustained outage clears the ticket and re-pins the client to a live follower.
  • Writes keep using the separately bearer-gated Write-master endpoint from the dashboard. They still pass through your API server and never expose the replicator token to the browser.

The reason there’s one write master at all is the correctness contract: one total write order is what lets every follower and every browser converge without a conflict-resolution protocol. The master accepts concurrent transactions and commits them into that one order; “one master” does not mean one request at a time.

Next steps