Relational catalogs for application data

RAQ — relational queries that compose across client and server

Clients express the data they need. Servers bind that query to application-owned relations, add policy, and compile the combined tree to SQL.

A client query flowing through a server-owned catalog boundary to SQL

The client query is only one part of the tree

import { project, relation, runQuery, where } from "@raquery/query";

const orders = project(
  where(relation("profile"), "status == 'open'"),
  ["name", "total", "status"]
);

const result = await runQuery("/profile", orders, { store_id: 42 });

The client can filter and project orders, but it does not decide what orders means. On the server, that public name is itself a relational expression:

query = profile_catalog.apply(request.query)   # bind public relations
query = enforce_store_scope(query, store_id)  # compose authorization
plan = compile_sql(query)                      # compile the whole tree

profile_catalog.apply replaces the public source with the application’s own joins, projections, and renames. enforce_store_scope adds another relational selection. The compiler never receives three disconnected concerns—it receives one tree.

That is the useful trick behind RAQ: client intent, the exposed data model, and server policy remain the same kind of thing, so they compose before execution.

What falls out of that model

RAQ stays close to relational algebra and SQL while giving applications control over the relational world a client is allowed to query.

Try the smallest complete example

The five-minute quick start builds a query, shows its serialized form, and evaluates it over local rows. Then run a server to see the same query cross a catalog boundary.