Skip to main content

7 posts tagged with "codegen"

View All Tags

Generated APIs for domain-driven development

· 2 min read
TeaQL Code Gen
Core Contributor

Domain-driven development breaks down when the domain language disappears from the code path.

That happens easily in data-heavy systems. The model may be discussed in design sessions, but implementation code becomes SQL strings, mapper files, repository methods, DTOs, and service glue.

TeaQL uses generated APIs to keep the domain model visible.

Model First

TeaQL starts from a domain model:

  • entities;
  • scalar fields;
  • object relations;
  • child lists;
  • constants;
  • validation expectations;
  • data-service targets.

The generator turns that model into APIs. Application code then works with the generated vocabulary instead of reconstructing it by hand.

Queries as Domain Language

Generated query methods make intent explicit:

Q.orders()
.filterByMerchant(ctx.getMerchant())
.selectCustomer(Q.customers().selectName())
.selectLineItemList(Q.lineItems().selectSku().selectQuantity())
.countLineItems()
.executeForList(ctx);

The code names the domain structure:

  • orders;
  • merchant;
  • customer;
  • line items;
  • SKU;
  • quantity.

That is more reviewable than a service method that hides several mapper calls and response transformations.

Relations Matter

DDD systems are rarely flat. Relations carry meaning:

  • an order has line items;
  • a merchant belongs to a platform;
  • an employee belongs to a department;
  • a ticket has comments and assignments.

TeaQL relation loading APIs give those relationships a generated shape. The runtime can still decide how to execute the load efficiently.

Graph Writes

Domain changes often arrive as object graphs:

  • create an order with lines;
  • update a parent and merge children;
  • remove a child row;
  • attach an existing reference;
  • keep missing children for a relation that should not delete absent rows.

TeaQL Rust models this through graph nodes, graph operation state, mutation planning, and transaction boundaries. Java TeaQL carries the same high-level idea through entity save and graph-oriented runtime behavior.

Validation and Runtime Policy

Domain logic is not only data shape. It also includes:

  • required fields;
  • status transitions;
  • tenant rules;
  • permission policy;
  • audit metadata;
  • language-aware validation messages.

TeaQL keeps those concerns near runtime context and generated model hooks instead of scattering them across controllers.

Why Generate?

Handwritten domain APIs are possible. The problem is consistency.

Generation gives every entity the same baseline:

  • field selectors;
  • filters;
  • relation selectors;
  • aggregate helpers;
  • request builders;
  • checker hooks;
  • behavior hooks;
  • runtime registration.

Teams can then focus on the parts of the domain that are actually special.

The Short Version

Generated APIs are not a shortcut around domain modeling.

They are a way to make the domain model executable, reviewable, and reusable across application code, runtime providers, and AI coding tools.

TeaQL Rust: From Java Feature Parity to Multi-database Runtime

· 3 min read
TeaQL Code Gen
Core Contributor

TeaQL started with a mature Java implementation. The Rust work does not try to clone every Java framework feature. It carries over the high-level programming model and rebuilds the runtime in Rust.

That shift matters.

Java proved the generated business API style. Rust turns that style into a runtime direction for PostgreSQL, MySQL, SQLite, embedded SQLite, and memory-backed tests.

What Carries Over from Java

The useful Java ideas are not Spring-specific. They are TeaQL-specific:

  • generated Q APIs;
  • readable query builders;
  • field and relation selectors;
  • SmartList style result metadata;
  • runtime context;
  • checker infrastructure;
  • translated validation messages;
  • mutation events;
  • graph writes;
  • relation enhancement;
  • relation aggregate enhancement;
  • safe value access.

Rust implements those ideas with Rust types, traits, crates, and provider registration.

What Does Not Carry Over Directly

Some Java features should not be copied blindly:

  • Spring Boot autoconfiguration;
  • Java GraphQL generation;
  • Java BaseService/controller conventions;
  • reflection-style runtime mechanisms;
  • the full Java database dialect matrix.

Rust needs a Rust-native shape. The runtime should be explicit, crate-based, and provider-backed.

The Rust Workspace

The Rust runtime is split by responsibility:

  • teaql-core for metadata, values, query model, entity traits, and SmartList<T>;
  • teaql-sql for SQL compilation and dialect behavior;
  • teaql-runtime for UserContext, repositories, checkers, events, relation enhancement, graph writes, and memory execution;
  • teaql-macros for #[derive(TeaqlEntity)];
  • provider crates for SQLx PostgreSQL, SQLx MySQL, SQLx SQLite, and rusqlite SQLite.

This keeps the core runtime separate from database adapters.

Generated Rust Crates

teaql-code-gen can emit Rust service crates. A generated crate can include:

  • entity structs;
  • Q facade;
  • generated request builders;
  • behavior skeletons;
  • checker skeletons;
  • runtime module registration;
  • repository and behavior registries;
  • provider-backed runtime helpers.

Application code then uses generated APIs:

let platforms = Q::platforms()
.select_merchant_list_with(
Q::merchants()
.select_name()
.which_names_contain("TeaQL"),
)
.execute_for_list(&ctx)
.await?;

Multi-database Runtime

The Rust direction is provider-based:

  • SQLx PostgreSQL for production-grade PostgreSQL backends;
  • SQLx MySQL for common enterprise MySQL systems;
  • SQLx SQLite for local-first apps, tests, and small services;
  • rusqlite SQLite for embedded and multi-architecture deployments;
  • MemoryRepository for no-database tests and demos.

The generated API should stay stable while the provider changes below it.

Current State

The Rust runtime is already useful for core generated-domain paths:

  • typed queries;
  • relation loading;
  • grouped aggregates;
  • graph writes;
  • checkers;
  • events;
  • schema bootstrap;
  • SQL debug logs;
  • generated high-level Q API validation against SQLite and PostgreSQL paths.

Full Java feature parity is not the only metric. The better metric is whether Rust has a coherent generated business API runtime.

That is now the direction.

What is TeaQL?

· 2 min read
TeaQL Code Gen
Core Contributor

TeaQL is a generated business API platform for systems where the domain model is more important than the storage plumbing around it.

Instead of asking developers to repeatedly write repositories, SQL fragments, DTO stitching, relation loading code, and validation glue, TeaQL starts from a domain model and generates APIs that read like business intent.

The current positioning is simple:

Generated Business APIs. Java-proven. Rust-powered. Multi-database ready.

The Core Idea

TeaQL separates the business API from the runtime provider.

Domain Model
-> TeaQL Generator
-> Generated Q API
-> Runtime Provider
-> MySQL / PostgreSQL / SQLite / Memory / Edge / Agent

Application code should talk to generated APIs. Runtime code should decide how those APIs execute against a database, memory repository, embedded store, or service runtime.

What TeaQL Generates

Depending on the target stack, TeaQL can generate:

  • entity types;
  • query builders;
  • safe expression helpers;
  • relation loading helpers;
  • checker and behavior skeletons;
  • graph save entrypoints;
  • runtime module registration;
  • agent guidance for generated APIs.

In Java, this appears as fluent generated request APIs. In Rust, generated crates expose Q::entities() style query builders over teaql-rs.

Why It Matters

Large business systems repeat the same concepts across many screens and services:

  • users and roles;
  • orders and line items;
  • customers and accounts;
  • status counts and dashboards;
  • parent-child graph saves;
  • tenant, permission, audit, and localization policies.

Without a generated business API layer, those concepts spread across SQL, mapper XML, repositories, service methods, DTOs, validators, and frontend-specific response shaping.

TeaQL keeps the model vocabulary visible in the code path.

Java and Rust

Java TeaQL is the mature, proven path for enterprise systems and Spring Boot services.

Rust TeaQL is the runtime direction for generated domain APIs across PostgreSQL, MySQL, SQLite, embedded SQLite, and memory-backed tests.

The two stacks are not identical, and they do not need to be. They share the programming model: generated APIs over a domain model, executed through a runtime boundary.

A Small Example

User userOrderInfo = Q.users()
.filterWithId(userId)
.countOrder()
.statsFromOrder("statusWithCount", Q.orders().count().groupByOrderStatus())
.selectOrderList(
Q.ordersWithId()
.selectOrderId()
.selectDate()
.offset(0, 10)
.countLineItems()
)
.execute(context);

This is not just a query. It is the shape of a business page expressed through generated APIs.

TeaQL in One Sentence

TeaQL turns domain models into stable business APIs that humans and AI tools can read, compose, and run across Java, Rust, and multiple database providers.

Why AI-generated software needs deterministic business APIs

· 2 min read
TeaQL Code Gen
Core Contributor

AI coding tools can produce useful application code quickly. The problem is not speed. The problem is boundary control.

If an AI tool has to infer persistence behavior from scattered SQL, repositories, mapper XML, DTOs, and service conventions, it will eventually guess wrong.

TeaQL exists to make that boundary deterministic.

The Guessing Problem

When AI writes data-access code directly, it must infer:

  • table names;
  • column names;
  • relation cardinality;
  • tenant filters;
  • permission rules;
  • deleted-row semantics;
  • transaction boundaries;
  • pagination rules;
  • response shape;
  • database dialect differences.

Some of these guesses can pass a simple test and still be wrong in production.

The Business API Boundary

TeaQL generates APIs from the domain model. That means AI code can compose with named business methods instead of inventing storage behavior.

let orders = Q::orders()
.select_customer_with(Q::customers().select_name())
.select_line_item_list_with(Q::line_items().select_sku())
.which_statuses_are("PAID")
.page(1, 20)
.execute_for_list(&ctx)
.await?;

The model controls what fields and relations exist. The runtime controls how the query executes. The AI composes within those boundaries.

Deterministic Does Not Mean Rigid

A deterministic API can still be expressive:

  • filters can be combined;
  • relation loads can be nested;
  • aggregates can be grouped;
  • graph writes can save parent and child objects together;
  • provider-specific behavior can be selected below the API.

The point is that the API surface is stable and reviewable.

Better Prompts

TeaQL also makes prompts smaller.

Instead of giving an AI tool the entire schema, SQL examples, repository conventions, and DTO rules, a team can provide a generated API guide:

Use TeaQL Q APIs for reads.
Use generated relation selectors.
Execute through UserContext.
Do not write raw SQL unless explicitly requested.
Use graph save for parent-child persistence.

That is a stronger contract than a long explanation of database structure.

Runtime Safety

The generated API is only half the story. Runtime boundaries matter too.

TeaQL keeps execution behind context and provider layers:

  • Java uses UserContext as the runtime boundary.
  • Rust uses teaql_runtime::UserContext, repository registries, behavior hooks, and provider registration.
  • Database providers execute against PostgreSQL, MySQL, SQLite, rusqlite, or memory.

AI-generated application code should not own those decisions.

The Short Version

AI tools are fast at composition. They are unreliable at reconstructing business rules from infrastructure code.

TeaQL gives them deterministic business APIs to compose.

Building a DDD Data Runtime with Generated Typed Queries in Rust

· 8 min read
TeaQL Code Gen
Core Contributor

TeaQL is a data layer for applications where the domain model is the center of the system rather than a thin mapping over tables. The current Rust implementation carries over ideas from the Java TeaQL stack, but with a smaller scope: PostgreSQL, SQLite, a Rust-native query AST, generated typed APIs, and no web framework dependency.

The goal is direct: instead of writing most application data access as handmade repository methods or raw SQL fragments, a domain model can generate a Rust crate with entity types, relation metadata, query builders, checker hooks, behavior hooks, and graph-save entrypoints.

Application code then works with a high-level API:

let platforms = Q::platforms()
.select_merchant_list_with(
Q::merchants()
.select_name()
.which_names_contain("TeaQL"),
)
.execute_for_list(&ctx)
.await?;

This is not meant to replace every way of using SQL from Rust. If a service is mostly carefully tuned SQL, direct sqlx is probably a better fit. If the preferred abstraction is an ORM with a large Rust ecosystem, Diesel or SeaORM will be more familiar. TeaQL is aimed at a different case: large domain models where repeated relation loading, graph persistence, validation, and statistics queries become their own layer of application logic.

Why Generate the API?

The original pressure came from systems where the same entity model needed to support several kinds of behavior:

  • ordinary list and detail queries;
  • nested relation loading, including paths such as merchant.platform or platform.merchant_list;
  • graph writes where a parent object and children are committed together;
  • additive schema bootstrap for development and tests;
  • checker and validation logic that can inspect and fix entities;
  • simple and grouped statistics;
  • JSON serialization and JSON-expression style search.

You can build all of that by hand, but the code tends to become repetitive in two places. First, relation names and field names are repeated across query methods, repository code, and validation code. Second, application developers end up switching between typed domain objects and untyped row maps. TeaQL tries to keep the generated surface typed while letting the runtime keep a generic query and graph model internally.

For example, a generated service crate exposes Q::platforms() and Q::merchants() rather than asking application code to construct SelectQuery::new("Platform") directly. Low-level query objects still exist, but they are not the normal application-level API.

Runtime Pieces

The Rust workspace is split into small crates:

  • teaql-core: values, records, entity descriptors, query AST, expressions, commands, and SmartList<T>;
  • teaql-sql: SQL compilation and dialect-neutral compiled query types;
  • teaql-runtime: UserContext, repository resolution, behavior hooks, checker hooks, graph writes, relation enhancement, events, and optional SQLx executors;
  • teaql-macros: #[derive(TeaqlEntity)] for descriptors and typed record/entity mapping;
  • teaql-provider-sqlx-postgres, teaql-provider-sqlx-sqlite, teaql-provider-sqlx-mysql, and teaql-provider-rusqlite: provider adapters and dialect-specific execution paths.

Generated crates sit above those runtime crates. A generated CRM or ERP service, for example, exports entities such as Platform and Merchant, a Q query facade, behavior skeletons, checker skeletons, repository registration, and runtime module assembly helpers.

Query Construction

TeaQL has a generic query AST, but generated code provides a domain-specific facade. Instead of this in application code:

let query = SelectQuery::new("Merchant")
.project("id")
.project("name")
.filter(Expr::contains("name", "tea"));

the generated API can expose:

let merchants = Q::merchants()
.select_name()
.which_names_contain("tea")
.order_by_create_time_desc()
.page(1, 20)
.execute_for_list(&ctx)
.await?;

Relation loading uses the same style:

let platforms = Q::platforms()
.select_merchant_list_with(
Q::merchants()
.select_name()
.select_platform(),
)
.execute_for_list(&ctx)
.await?;

One implementation detail mattered here: when a child is attached to a parent relation list, the reverse object relation should be populated too. In the example above, each merchant in platform.merchant_list should have its platform relation set. Otherwise, the result is typed but not really a domain object graph.

Graph Writes

TeaQL has a save_graph path for committing complex objects. In generated crates, application code can call a typed save helper:

merchant
.update_name("TeaQL Merchant")
.update_platform_id(1_u64)
.save(&ctx)
.await?;

Internally, the runtime turns that object into a graph plan. The plan classifies nodes by entity and operation: create, update, delete/remove, or reference. It then batches compatible work where possible and runs the graph write inside a transactional executor.

The interesting part is not only inserting children. Updating a graph means answering questions like:

  • is this child new, already present, a reference, or explicitly removed?
  • should missing children be soft-deleted or left alone?
  • should a relation write attach a foreign key, or is it detached?
  • if a reference points at a deleted row or the wrong version, should the graph write fail?

The Rust runtime currently supports nested create/update graph writes, reference-only nodes, explicit remove nodes, keep-missing relation metadata, duplicate child-id rejection, and transaction rollback for SQLite and PostgreSQL SQLx executors.

Schema Bootstrap

For local development and generated-service tests, TeaQL can bootstrap a schema from entity descriptors:

ctx.ensure_sqlite_schema().await?;

The current scope is intentionally conservative. It creates missing tables and adds missing columns. It does not try to be a destructive migration tool: no column drops, no primary-key rebuilds, and no automatic type rewrites.

That line is important because generated domain models change frequently. The bootstrap path should be safe enough for local and CI use, not pretend to replace a real production migration process.

Checkers and Domain Validation

A checker is not just a validator that rejects a row. It can inspect an object, add structured check results, and sometimes fix fields before persistence.

The generated Rust checker support lets application code write typed checker logic instead of manually reading from a Record:

impl MerchantCheckerLogic for MerchantNameChecker {
fn check_and_fix_merchant(
&self,
_ctx: &UserContext,
entity: &mut Merchant,
status: CheckObjectStatus,
location: &ObjectLocation,
results: &mut CheckResults,
) {
if status.is_create() {
self.required_text(&entity.name(), "name", location, results);
}

self.min_string_length(&entity.name(), "name", 3, location, results);

if entity.name() == "fix" {
entity.update_name("fixed");
}
}
}

The runtime still stores the common checker interface at the record level, but the generated adapter maps records into typed entities before calling the checker. That keeps the public application code close to the domain model while preserving a generic runtime path.

Statistics

TeaQL queries can carry aggregate projections and relation aggregate metadata. The current runtime supports simple aggregates, grouped aggregates, Decimal results for SQL aggregate output, relation count/statistic attachment, and database-column-to-entity-property mapping for relation aggregate keys.

The generated Q APIs can express both simple statistics and relation statistics. For example, a service can count child rows from a parent query without asking application code to hand-build the join every time.

This area is useful but still evolving. The runtime has working SQL and memory paths for the core cases, while broader Java parity still needs more work around memory subqueries and richer relation aggregate shapes.

Tradeoffs

The most obvious tradeoff is generated code. TeaQL generates a lot of Rust. That cost shows up as compile time, larger diffs, and the need to keep templates disciplined. The benefit is that application code gets a stable, typed facade over a large domain model.

Another tradeoff is that the runtime is not purely compile-time checked. The generated APIs are typed, but the runtime still has a generic query AST, record model, and descriptor registry. That gives it flexibility for dynamic projections, aggregate rows, JSON-style search, and graph planning, but it means some mistakes are caught by generated crate tests rather than by Rust types alone.

The final tradeoff is scope. The Rust rewrite is not trying to clone every Java TeaQL feature or support every database. PostgreSQL and SQLite are enough for now. Web rendering, GraphQL integration, and broad database dialect support are outside the current Rust scope.

What Works Today

The current Rust runtime and generated crate tests cover:

  • SQLite schema bootstrap and additive column changes;
  • PostgreSQL schema bootstrap with SQLx;
  • CRUD, optimistic locking, soft delete, and recover;
  • typed entity fetch into SmartList<T>;
  • nested relation enhancement;
  • complex object commit through graph writes;
  • transaction rollback for graph writes;
  • generated Q APIs against SQLite;
  • typed checker adapters from generated crates;
  • JSON serialization and JSON-expression search paths;
  • simple aggregates, grouped aggregates, and relation aggregate statistics.

The public examples can be run with:

cargo run -p teaql-examples --bin sqlite_schema_crud
cargo run -p teaql-examples --bin sqlite_relations_graph

The first command shows schema bootstrap and CRUD against in-memory SQLite. The second saves an object graph and reloads nested relations.

What Is Not Done

The biggest gaps are:

  • more complete memory repository parity for relation enhancement and subquery execution;
  • richer checker semantics, especially nested typed object locations and domain-specific labels;
  • richer event payloads with old/new values and typed snapshots;
  • more value types such as UUID and bytes;
  • a decision on whether Rust needs a higher-level service layer above the repository/runtime APIs.

Those gaps are real. They are better kept visible than hidden behind a larger feature list.

Why This Shape Is Worth Exploring

Most Rust database libraries are good at one of two layers: explicit SQL, or a database-centric ORM. TeaQL explores a third shape: generated domain APIs over a generic runtime that understands entity graphs, relation enhancement, validation, and statistics.

That shape will not fit every codebase. It is most useful when the model is large enough that the generated API becomes an asset, and when the team wants the same domain semantics to appear in queries, graph writes, checkers, and schema bootstrap.

Generated Query APIs vs Handwritten SQL

· 5 min read
TeaQL Code Gen
Core Contributor

TeaQL generates query APIs such as Q::platforms() and Q::merchants(). That immediately raises a fair question: why not just write SQL?

The short answer is that TeaQL is not trying to replace handwritten SQL everywhere. Generated query APIs are useful when the repeated work is not the SQL text itself, but the domain semantics around the SQL: field names, relation names, relation loading, statistics, validation, JSON search, and graph-shaped results.

The Handwritten SQL Case

There are many cases where handwritten SQL is the right tool:

  • a query is performance-critical and carefully tuned;
  • the shape is reporting-specific rather than domain-object-specific;
  • the team wants exact control over joins, hints, indexes, and execution plans;
  • the service is small enough that generated APIs would add more weight than value.

TeaQL should not get in the way of those cases. The Rust runtime keeps a generic query AST and SQL compiler, but the design does not require every query to be expressed through generated methods.

If explicit SQL is the clearest abstraction, use explicit SQL.

The Generated API Case

Generated APIs become useful when a large domain model produces many ordinary queries with the same repeated structure:

let merchants = Q::merchants()
.select_name()
.select_platform_with(Q::platforms().select_name())
.which_names_contain("tea")
.order_by_create_time_desc()
.page(1, 20)
.execute_for_list(&ctx)
.await?;

This is not shorter than SQL in every case. The value is that the query is tied to the generated domain model:

  • fields are exposed as methods;
  • object relations are selected by relation name, not storage column name;
  • readable filters follow the model vocabulary;
  • subqueries can be constructed from the target entity's own generated API;
  • execution returns typed entities or typed SmartList<T> collections.

That matters when the model is large and the same entity relationships appear across many services, pages, and business workflows.

Relation Semantics

A generated API can encode relation semantics that plain SQL does not name.

For example:

let platforms = Q::platforms()
.select_merchant_list_with(
Q::merchants()
.select_name()
.select_platform(),
)
.execute_for_list(&ctx)
.await?;

The application is not just asking for a join. It is asking for a platform list where each platform contains a merchant_list, and each merchant can carry its reverse platform relation when selected. That object shape is a domain result, not only a SQL result set.

The runtime still compiles to SQL and fetches records. The generated API gives the caller a domain-oriented way to request the shape.

Subqueries Without Stringly-Typed Entity Names

TeaQL still has SelectQuery internally. Application code can use it directly, but generated crates should prefer the entity-specific Q entrypoints:

let platforms = Q::platforms()
.select_merchant_list_with(
Q::merchants()
.which_names_are("TeaQL Merchant"),
)
.execute_for_list(&ctx)
.await?;

This keeps the application out of string-based construction such as SelectQuery::new("Merchant") for ordinary code. The generic query type remains available for runtime and escape-hatch scenarios.

Statistics

Large business systems rarely stop at list queries. They also need counts, sums, grouped aggregates, relation counts, and derived summary rows.

A generated API can give those operations domain names:

let platforms = Q::platforms()
.select_name()
.count_merchant_list_as("merchant_count")
.execute_for_list(&ctx)
.await?;

The runtime can attach relation aggregate results back to parent rows, while the generated request builder keeps the caller focused on domain concepts.

This does not remove the need for handwritten reporting SQL. It gives ordinary domain statistics a consistent path.

Why Keep a Generic Runtime?

If generated APIs are useful, why not make the whole runtime typed?

Because TeaQL still needs dynamic behavior:

  • generated service crates vary by domain model;
  • aggregate rows can contain dynamic projection fields;
  • JSON-expression search and raw SQL escape hatches need flexible query metadata;
  • graph planning operates across entity types;
  • relation enhancement starts with records and then maps into typed entities.

The generated layer is typed for application ergonomics. The runtime layer is generic so it can plan, compile, enhance, validate, and execute across many generated models.

Where Generated APIs Hurt

Generated code is not free:

  • it increases compile time;
  • it creates large diffs when templates change;
  • it can hide SQL details if developers treat it as magic;
  • it needs strong generated-crate tests to catch template regressions.

TeaQL tries to offset that with explicit generated code, local runtime crates, and generated service tests that run real SQLite scenarios. The code is not hidden behind a server or reflection system; it is Rust source that can be inspected.

Still, generated APIs should be used where they buy something. A small service with ten queries may be better served by direct sqlx.

A Practical Boundary

The boundary I use is this:

  • if the query expresses a domain object shape, relation graph, validation path, or repeated business filter, prefer the generated Q API;
  • if the query expresses a one-off report, hand-tuned performance path, or database-specific operation, prefer handwritten SQL;
  • if generated code starts hiding a critical execution detail, drop down a level.

TeaQL's design only works if the lower level remains available.

The Real Goal

The goal is not to win an argument against SQL. SQL remains the execution language and often the best authoring language.

The goal is to avoid making every application workflow manually rediscover the same domain relationships. In a large model, the generated API becomes a shared vocabulary: Q::platforms(), select_merchant_list_with(...), have_platform(), count_merchant_list_as(...), and so on.

That vocabulary is where TeaQL is useful. Not because SQL is bad, but because large domain models need more than SQL strings to stay coherent.

Multi-Stack Code Generation

· One min read
TeaQL Code Gen
Core Contributor

TeaQL Code Gen learned Rust. One model definition now produces both Java and Rust artifacts.

Separate Stack Templates

templates/
java/
entity.java.vm
repository.java.vm
rust/
entity.rs.vm
query.rs.vm

Each stack has isolated templates. No cross-language leakage.

Rust Lib Generators

// Generated from the same .teaql model
pub struct User {
pub id: u64,
pub name: String,
}

impl TeaQLEntity for User {
const TABLE: &str = "user_t";
}

Typed Query Return Types

let names: Vec<String> = db.query(User::name())
.filter(User::active().eq(true))
.list()
.await?;

The generator produces typed projections. User::name() returns String, User::class() returns User.

Explicit Aggregation APIs

let stats = db.query(Order::class())
.group_by(Order::status())
.agg(Order::total().sum())
.agg(Order::id().count())
.list()
.await?;

Group-by and rollup DSLs are fully typed. The macro validates aggregation compatibility at compile time.

Relation Generation

let user = db.load(User::class(), id)
.with(User::orders())
.await?;

User::orders() is generated from the model's relation definition. No hand-written joins.