Queen is one repository with two Rust binaries in it, a Vue dashboard that is compiled into both of them, five client SDKs, a Dockerised test matrix and this site. Nothing is generated at runtime: the SQL, the dashboard and the version string are all baked into the binary at compile time, which makes the build order load-bearing in ways a normal Rust project’s is not. That is the main thing this page is for.
What is in the tree
- server/
- src/
- handlers/
- main.rs
- schema.rs
- sql/
- schema.sql
- procedures/
- webapp/
- dist/
- server.json
- build.rs
- Dockerfile
- src/
- proxy/
- src/
- migrations/
- console/
- scripts/
- app/
- src/
- vite.config.js
- clients/
- client-js/
- client-py/
- client-go/
- client-laravel/
- client-cpp/
- client-cli/
- test/
- run.sh
- compose/
- runners/
- webdoc/
- benchmark-queen/
- examples/
- Dockerfile
- go.work
| Path | What it is |
|---|---|
server/ |
The broker. Crate queen, binary queen. Axum 0.7 + tokio + deadpool-postgres. |
server/sql/ |
schema.sql plus 23 procedures/*.sql files (001–023). All queue semantics live here, and all of it is embedded in the binary. |
server/webapp/dist/ |
The built dashboard the binary embeds. Gitignored, so a clean clone does not have it. |
server/server.json |
Name and version. The single source of the version string. |
proxy/ |
The multi-tenant gateway, one per cell. Binary queen-proxy, its own PostgreSQL schema under migrations/. |
proxy/console/ |
The operator console SPA. Unlike the dashboard, its dist/ is committed. |
app/ |
Vue 3 + Vite dashboard. Builds into server/webapp/dist. |
clients/ |
Five client SDKs: JavaScript (npm queen-mq), Python (PyPI queen-mq), Go, PHP/Laravel (Packagist smartpricing/queen-mq) and a header-only C++ client. Also queenctl, the operator CLI. |
test/ |
The Docker Compose test matrix. One entry point, run.sh. |
webdoc/ |
This site. |
benchmark-queen/ |
Archived benchmark sessions, their loaders and their raw output. |
Dockerfile |
The full-stack image: dashboard, broker and queenctl. |
go.work |
Go workspace joining client-cli and client-go. |
Three things in the tree are stale: DEVELOPING.md, developer/*.md, and the docs/
HTML site. They document the retired C++ broker (lib/, libqueen, uWebSockets,
NUM_WORKERS, make deps), none of which exists here any more. CONTRIBUTING.md still sends
a new contributor to DEVELOPING.md. Treat all of them as history; this site and the source
are current.
The build order
server/ will not compile from a clean clone until the dashboard exists, because
server/src/handlers/static_files.rs embeds it:
#[derive(RustEmbed)]
#[folder = "webapp/dist"]
struct Assets;server/webapp/dist is gitignored, and rust_embed fails at compile time when the folder is
missing. app/vite.config.js sets outDir to ../server/webapp/dist, so building the
dashboard is what puts it there. The proxy embeds the same directory through
proxy/src/webapp.rs (#[folder = "../server/webapp/dist"]) rather than keeping a
second copy, so one dashboard build serves both binaries.
-
Build the dashboard once. From
app/:npm cinpm run build -
Build the broker. From
server/:cargo build --releaseThe binary lands at
server/target/release/queen. -
Build the proxy, if you are touching it. From
proxy/:cargo build --release
Two things the binary carries besides the dashboard. server/build.rs reads
server/server.json and exports its version as QUEEN_VERSION, which is what /health,
the boot log and queenctl ping report, so a version bump is a rebuild. And every file
under server/sql/ is include_str!’d by server/src/schema.rs.
Editing SQL means rebuilding
All queue semantics live in PostgreSQL stored procedures, and every one of them is compiled into the binary:
const SCHEMA_SQL: &str = include_str!("../sql/schema.sql");
const PROCEDURES: &[(&str, &str)] = &[
("001_log_schema.sql", include_str!("../sql/procedures/001_log_schema.sql")),
// ... 22 more, in the order they must be applied
];The applier runs at every boot, in the lexical order of the list above, under a session
advisory lock so replicas serialise rather than race. Every statement is idempotent
(CREATE OR REPLACE, IF NOT EXISTS, ADD COLUMN IF NOT EXISTS), which is what makes
re-applying on every boot safe. It only ever creates: the deployment model is always-virgin
(a deployment starts from an empty database, and there are no migration files), so each table
and function is defined exactly once, by exactly one file. Lexical order is still not cosmetic:
the table files (001_log_schema, 002_streams_schema) must land before the SQL-language
function bodies that resolve those tables at creation time, and the partition-counter trigger
attachment (020_log_partition_counters) must land after both the table it attaches to and the
functions it binds (019_worker_metrics). Setting QUEEN_APPLY_SCHEMA=0 skips the whole step.
The dashboard has the opposite failure mode and it is worth knowing the difference: a debug
rust_embed build reads assets from disk, so a dashboard change appears to take effect
after only the npm run build. A --release build does not. include_str! is unconditional:
SQL is always frozen at compile time, in both profiles.
The development loop
For broker work, a container for PostgreSQL and the binary on the host is enough. Give it a
port that is not one of the reserved ones (proxy/CONTRACTS.md reserves 5432, 5455,
5457, 5460, 5464, 6632, 6682, 6690 and 6702 for other local stacks).
-
Start a PostgreSQL. The broker creates the
queenandqueen_streamsschemas itself and needs no extensions.docker run -d --name qpg -p 5433:5432 -e POSTGRES_PASSWORD=postgres postgres:16 -
Run the broker against it. It applies the schema, then binds
PORT(default 6632).PG_HOST=localhost PG_PORT=5433 PG_PASSWORD=postgres ./target/release/queen -
Confirm.
/healthdoes a real database round-trip, so a 200 means broker plus PostgreSQL plus schema.curl localhost:6632/health
For anything that involves authentication, tenancy, quotas or 429s, use the proxy’s
development cell instead. proxy/scripts/dev-cell.sh up builds both binaries in debug,
starts two PostgreSQL containers (the proxy’s own state database on 5465 and a cell database
on 5466), starts the broker on 6710 with QUEEN_TENANCY_HEADER=true, starts the proxy on
6711, and seeds a development tenant, cluster and API key. It writes logs and pid files under
proxy/.devcell/, which is gitignored. The proxy comes up in shadow mode by default;
QUEEN_PROXY_ENFORCE=true scripts/dev-cell.sh up turns real rate-limit responses on, which
is what the 429 checks need.
proxy/scripts/dev-cell.sh upThe dashboard’s own dev server (npm run dev in app/) listens on 4000 and proxies /api,
/auth, /health and /metrics to http://localhost:6711: the proxy, not the broker,
because auth and tenancy are what the app has to behave correctly against.
QUEEN_DEV_UPSTREAM=http://localhost:6632 switches it to broker-direct for debugging the
operator escape hatch. More on the dashboard itself is in Self-hosting.
Container builds
There are two Dockerfiles and they are not interchangeable.
| File | Contents | Used by |
|---|---|---|
server/Dockerfile |
Broker only. Copies src, sql and a pre-built webapp into a rust:1-bookworm stage, runs on debian:bookworm-slim. |
test/run.sh and the tests CI workflow, tagged queen:test |
Dockerfile (root) |
Four stages: Node builds the dashboard, Rust builds the broker, Go builds queenctl, and an ubuntu:24.04 runtime carries all three plus the PostgreSQL 18 client tools. |
the published image |
server/Dockerfile expects server/webapp/ to already exist in its build context, so it is
the “I built the dashboard already” image. The root Dockerfile builds the dashboard itself and
therefore works from a clean clone.
Go modules
clients/client-go and clients/client-cli are separate Go modules, and go.work at the
repository root joins them so a change to the client is immediately visible to the CLI without
publishing. End users are unaffected: go install ignores go.work and resolves through the
public proxy using the versions pinned in client-cli/go.mod. The CLI’s own test suite needs
that workspace, which is why the cli runner image is built with it.
Conventions
- English in committed material. Source comments, documentation and commit messages.
Several benchmark session notes under
benchmark-queen/are in Italian; they are working records, not published documentation. - One logical change per pull request, with the component in the title (
server:,queen_proxy:,client-py:,sql:). - Run the matrix for what you touched. See Testing.
- Regenerate the derived documentation when you change the router, the configuration, the metrics, the proxy’s route classifier or a published snippet. See The documentation site.
- Do not add a dependency that needs cmake or a system OpenSSL. Both crates are
deliberately
rustls+ringonly;server/Cargo.tomlrecords the reasoning at each dependency. This is why there is noreqwestand why JWKS fetching is a small in-tree HTTPS client (server/src/httpget.rs). - The licence is Apache 2.0, and contributions are under it.