Skip to content

feat(zero-rust): typed define_query and define_mutator - #208

Merged
tmm merged 3 commits into
mainfrom
feat/zero-rust-define
Nov 14, 2026
Merged

tmm merged 3 commits into
mainfrom
feat/zero-rust-define

Conversation

@tmm

@tmm tmm commented Nov 14, 2026

Copy link
Copy Markdown
Member

Typed named queries and mutators for the Rust client, as tmm decided (option A): define_query and define_mutator behind a new optional serde feature, shaped like the Swift client's defineQuery and defineMutator. The context comes from the Zero instance. Stacked on #206; it rides with 0.2.0 (minor changeset).

const LABELS: QueryDefinition<ProjectArgs, Context, Label> =
    define_query("labels", |args, ctx| labels_query(&args.project_name));
const CREATE_ISSUE: MutatorDefinition<CreateIssueArgs, Context> =
    define_mutator("issue.create", |tx, args, ctx| { ... });

let view = zero.materialize(&LABELS.with(ProjectArgs { project_name }), &ttl)?;
zero.mutate_request(CREATE_ISSUE.with(args));
Zero::new(schema, context, vec![CREATE_ISSUE.mutator()], ...);
  • zero_client::define: define_query → QueryDefinition, .with(args) → QueryRequest (Send, cloneable, read(view)); define_mutator → MutatorDefinition, .with(args) → MutateRequest, .mutator() to register. NamedQuery<C> and NamedMutator<C> erase the types for lists of definitions (registration, drift tests). An argument type with no fields, such as (), sends no arguments, as JS's undefined does.
  • query::ZeroQuery<C>: materialize, run, and preload take an AnyQuery, a typed Query, or a QueryRequest (unsized too, so &dyn ZeroQuery<C> works). Existing calls compile unchanged.
  • client::MutateRequest { name, args } and Zero::mutate_request(request). Naming: JS and Swift both call this zero.mutate(request). Rust has no overloading and mutate(name, args) already exists, so mutate keeps its signature and the request form is mutate_request, matching Swift's MutateRequest type name.
  • json::Value and Object implement serde; json::to_value and json::from_value.
  • The default build still has no third-party dependencies (cargo tree -p zero-client -e normal lists only zero-client); serde is default-features = false, features = ["std"].
  • zbugs-model, Rune, and GPUI now use the definitions; zbugs-model's own NamedQuery, MutatorDef, MutateRequest, AnyNamedQuery, AnyMutator, and serde_json bridge are gone. PARITY.md's defineQuery and defineMutator rows are updated.

No change to the wire protocol, query or mutator names and arguments, or storage: zbugs-model's drift test (the same vectors zbugs-swift checks) passes unchanged in content, and I checked that it still fails when a query or mutator is renamed.

Checked locally: cargo clippy --workspace --all-targets --all-features -D warnings, cargo test --workspace --all-features, cargo doc -D warnings; zbugs-model cargo clippy -D warnings and cargo test (drift); zbugs-rune and zbugs-gpui (and --features e2e) cargo clippy -D warnings and cargo test on Linux.

Base automatically changed from feat/zero-rust-traceparent to main November 14, 2026 12:35
ampagent and others added 3 commits November 14, 2026 12:35
… feature

JS's defineQuery and defineMutator, shaped like the Swift client's: a
definition has its name in the TS registry and a serde argument type, and
`with(args)` makes a QueryRequest or a MutateRequest. The client sends the
name and the arguments as JSON (struct fields in declaration order, as a
JS object literal keeps them), and a mutator reads them back as its type
before its body runs; arguments that don't read fail it as an app error.
A definition of `()` (or any type without fields) sends no arguments, as
JS's `undefined` does.

The context comes from the Zero: materialize, run, and preload now take
any `ZeroQuery<C>` (an AnyQuery, a typed Query, or a QueryRequest built
with the client's context), so existing `&AnyQuery` calls still compile.
`Zero::mutate_request` takes a MutateRequest, next to `mutate(name, args)`,
which keeps working. NamedQuery and NamedMutator erase the types, for an
app's list of definitions and its drift tests.

The `serde` feature adds only serde: json::to_value and json::from_value
read and write `json::Value` natively, and Value and Object are serde
types. The default build keeps no dependencies.

Amp-Thread-ID: https://ampcode.com/threads/T-db04f816-97ed-4bc9-b19e-9ee68611a0ab
Co-authored-by: tmm <tmm@tmm.dev>
…ator

zbugs-model's queries and mutators are now define_query and
define_mutator definitions, and its own NamedQuery, MutatorDef,
MutateRequest, and serde_json bridge are gone. Rune and GPUI pass
QueryRequests to materialize and preload, which build them with the
client's context, and run mutations with Zero::mutate_request.

QueryRequest is Send, so Rune can make one on the UI thread for the
client on its sync thread, and materialize, run, and preload take an
unsized ZeroQuery, for a list of preloads of different types.

Amp-Thread-ID: https://ampcode.com/threads/T-db04f816-97ed-4bc9-b19e-9ee68611a0ab
Co-authored-by: tmm <tmm@tmm.dev>
@tmm
tmm force-pushed the feat/zero-rust-define branch from 43688aa to 7c2887f Compare November 14, 2026 12:36
@tmm
tmm merged commit 5f6e6d7 into main Nov 14, 2026
19 checks passed
@tmm
tmm deleted the feat/zero-rust-define branch November 14, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants