Skip to content
Guides contents

GuidesQueries & schemas

Pinned queries

Keep a server query result current between requests, even with no subscribers, and read it without a live client.

View as Markdown

A pinned query is a live query whose result stays materialized with no subscribers. Use one for a frequently read public page, leaderboard, or service response. The engine updates the result as it applies source changes.

The consumer makes a one-shot request for rows. It does not need WebAssembly, browser sync, optimistic writes, or a UI framework.

Before you start

You need a Rindle deployment with live-query support and an API server connection. Define the query in the server query registry. Use the TypeScript query guide for supported shapes.

This example assumes the SQL-generated issue table from the manual synced-app setup, with id, title, and createdAt columns. It defines one public, fixed-size query and reads its rows from server code:

import { defineQuery, newQueryBuilder } from "@rindle/client";
import { createRindleApiServer, registerQueries } from "@rindle/api-server";
import { schema } from "../shared/schema.gen.ts";

const q = newQueryBuilder(schema);
const publicIssues = defineQuery("publicIssues", () =>
  q.issue.select("id", "title").orderBy("createdAt", "desc").limit(50),
);

const api = createRindleApiServer({
  rindle: {}, // Reads RINDLE_URL and RINDLE_DATABASE_TOKEN on the server.
  queries: registerQueries([publicIssues]),
  pinnedQueries: [{ name: "publicIssues" }],
});

try {
  await api.assertPins();
  const result = await api.readQuery({
    user: undefined,
    name: "publicIssues",
    args: null,
  });
  console.log(result.rows);
} finally {
  api.close();
}

The sample query is intentionally public. It has no per-user filter. For a private query, supply your authenticated request context and authorize it as shown in the API server guide.

assertPins() creates the configured materializations. Repeating the call reuses an existing result for the same canonical query. In a long-running service, create the API instance at startup, assert its pins, and close it at shutdown. Closing the API client’s transport does not unpin the daemon’s result.

Read the result

Your request handler can call api.readQuery or expose api.handleReadJson. Both resolve the named query under the request context and apply the configured query authorization. See the HTTP handler example for request wiring.

A one-shot response contains { rows, cvMin, queryKey }. It returns the current assembled rows without opening a subscription. The SSR integration uses the same read operation to seed a page before browser hydration.

Reading the matching pinned query reuses its maintained result. An unpinned one-shot query can also stay warm temporarily, controlled by readIdleTtlMs.

Choose what to pin

Pins resolve under pinUser, which defaults to undefined. Choose queries that are independent of the current viewer, such as a public topic list. Do not use a shared pin as a substitute for per-request authorization.

A request reuses the pin only with the same canonical query and visibility scope. Pins have no per-viewer visibility key. A read scoped by subject or routingKey can create a separate materialization, even with the same query arguments. Different arguments or authorization filters can also produce a separate result.

The current lease path also assigns a pinned policy to any query whose name appears in pinnedQueries. That includes other argument combinations leased under that name, not only the arguments asserted at startup. Prefer a dedicated named query with fixed or tightly bounded arguments. Otherwise, user-selected values can leave many distinct results pinned.

Pins consume memory and maintenance work while no one reads them. Initial materialization reads the starting data. Later work depends on the query, indexes, and affected rows. Each response also serializes the result.

Keep pins available

The daemon does not persist materialization state across restarts. Call assertPins() at startup and after a follower boot identifier changes. Pinned means retained with no subscribers, not durable across a restart.

For a fleet, configure pinFanout to assert pins on all live followers. Without it, assertPins() uses its configured daemon connection. The API server guide describes the lifecycle, and the deployment guide covers routing.

A pinned result is current for the source changes that its engine has applied. Replication lag can still separate a follower from the write authority. Use the deployment’s consistency guarantees when interpreting a read.