Skip to main content

TeaQL Adds Topcoat Support for Generated Rust Web Applications

· 5 min read
TeaQL Team
Core Team

TeaQL now supports Topcoat as a Rust web application target.

The new rust-web-topcoat target generates a server-rendered Topcoat application around the same TeaQL Rust domain crate used by our other application targets. It does not introduce a second shared core: a domain such as commerce continues to use commerce-core, while its framework-specific application crate is named commerce-web-topcoat.

The most important integration decision is the runtime boundary. Every HTTP request receives an independent TeaQL UserContext. TeaQL runtimes, repositories, executors, pools, and other TeaQL resources are accessed through that context and are never registered in Topcoat's application context.

A New Application Target, Not a New Domain Core

TeaQL separates generated domain capabilities from editable application code. Topcoat support follows that existing design:

generated/
├── rust-lib-core/
│ └── lib/ # <domain>-core
└── rust-web-topcoat/
├── Cargo.toml # <domain>-web-topcoat
├── src/
│ ├── main.rs
│ └── lib.rs
├── AGENTS.md
└── RUNTIME_CUSTOM_GUIDE.md

The generated Topcoat manifest uses a local path dependency on ../rust-lib-core/lib. Entities, typed queries, expressions, behaviors, checkers, and provider registration remain in the existing core crate. Topcoat-specific routing, pages, request metadata, and HTTP behavior belong in the web application.

This boundary lets teams add a Topcoat application without splitting or duplicating their domain model.

UserContext Is the TeaQL Resource Boundary

Topcoat provides typed request and application contexts. TeaQL deliberately uses only the request side of that integration.

The generated root layer performs the following work for each request:

  1. snapshot HTTP metadata;
  2. construct a new TeaQL UserContext;
  3. enhance its identity, trace, timezone, and web-request information;
  4. insert the UserContext into Topcoat's request context;
  5. continue routing.

Pages, routes, and components obtain TeaQL capabilities through:

let ctx: &UserContext = user_context(cx);

They can then use the generated domain API without reaching into a framework singleton or global container:

let merchants = Q::merchants()
.purpose("Show merchants on the current page")
.comment("Load the merchant list")
.execute_for_list(ctx)
.await?;

Writes keep the same audited boundary:

merchant
.audit_as("Approve the reviewed merchant")
.save(ctx)
.await?;

The generated AGENTS.md repeats these constraints so that developers and coding agents encounter them where application code is written:

  • reads using execute_for_list() or execute() require both purpose and comment;
  • writes using save() or update() require audit_as;
  • exact entity and request method names must come from generated source or model-aware TeaQL assist;
  • TeaQL resources must not be moved into Topcoat's application context.

Client Information Is Available Through UserContext

The request layer inserts a typed WebRequestInfo resource into UserContext. The generated WebContextExt API exposes:

  • remote_ip() with client_ip() as an alias;
  • user_agent();
  • request_uri();
  • request_method();
  • request_header(name) for additional headers.

X-User-Id, X-Trace-Id, and X-Timezone also enhance the corresponding built-in UserContext fields.

In this first integration, remote IP resolution reads the first X-Forwarded-For value and then falls back to X-Real-IP. Applications must only trust those headers when a trusted reverse proxy overwrites and supplies them. Topcoat's current serving path does not expose the accepted socket peer address through its request context, so deployments without such a proxy should treat remote_ip() as unavailable rather than trusting client-controlled input.

Keeping this information inside UserContext gives query policy, audit, tracing, and application services one consistent request-scoped source. HTTP metadata does not become a second access path around TeaQL's runtime boundary.

Generated Starting Points

The initial workspace includes:

  • a Topcoat router discovered from route macros;
  • a root request layer that creates the TeaQL context;
  • a server-rendered GET / page;
  • a JSON GET /health route;
  • database runtime initialization and schema setup when the selected provider requires it;
  • runtime and agent guidance beside the editable source.

The generated application uses Topcoat's router, layers, pages, routes, view syntax, and Tokio-based server. It is intentionally a starting point: teams can add layouts, components, forms, and application services while retaining the same domain crate and request-context contract.

Generate a Topcoat Workspace

Ask the configured TeaQL Generation Service for its live target catalog before generation:

cargo teaql services
cargo teaql evaluate --input model/main.xml

Generate the core and Topcoat application into sibling directories:

cargo teaql rust-lib-core \
--input model/main.xml \
--output generated/rust-lib-core

cargo teaql rust-web-topcoat \
--input model/main.xml \
--output generated/rust-web-topcoat

Then inspect the generated manifest and local instructions before building:

cd generated/rust-web-topcoat
cargo check
cargo run

Generation targets are served dynamically, so an installed client accepting the command does not by itself prove that a particular Generation Service deployment exposes rust-web-topcoat. The output of cargo teaql services is authoritative for the configured endpoint.

Verification and Current Scope

For this release, we added generator tests covering:

  • the new target's discovery and preview availability;
  • application and module naming;
  • reuse of the unchanged domain core dependency;
  • request-scoped UserContext insertion;
  • identity, trace, timezone, remote IP, User-Agent, and arbitrary-header metadata;
  • the absence of TeaQL resources in a Topcoat application context;
  • generated runtime and coding-agent instructions.

We also generated a concrete Topcoat workspace and passed cargo check against a local TeaQL Rust checkout.

This establishes the first supported integration shape: one existing TeaQL domain core, one editable Topcoat application, and one independent UserContext per request. Future framework capabilities can build on that shape without weakening the resource boundary.