TeaQL Adds Topcoat Support for Generated Rust Web Applications
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:
- snapshot HTTP metadata;
- construct a new TeaQL
UserContext; - enhance its identity, trace, timezone, and web-request information;
- insert the
UserContextinto Topcoat's request context; - 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()orexecute()require bothpurposeandcomment; - writes using
save()orupdate()requireaudit_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()withclient_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 /healthroute; - 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
UserContextinsertion; - 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.
