Skip to content

Consume generated proxies

A backend change should produce a compiler error where your frontend needs updating, not a surprise in production. Arc generates TypeScript command, query, and model types from discovered endpoints — for the shared frontend runtime. Run the generator for your backend, then update your consumers; never edit generated files.

You get completion for command fields and query arguments, typed responses, and compiler-guided repairs after a backend rename. The generated modules also give you concrete contracts to navigate instead of hand-maintained request shapes.

Current generated command and query modules import both @cratis/arc and @cratis/arc.react, even when you call them imperatively. Install their dependencies in your React app:

Terminal window
npm install @cratis/fundamentals @cratis/arc @cratis/arc.react react react-dom

Mount Arc above hook consumers. For build configuration, namespace mapping, source-file grouping, and output ownership, use the C# backend proxy-generation reference, the JVM TypeScript proxies guide, or the TypeScript source preview’s generator setup.

Arc is a standalone CQRS framework. A command can call an ordinary service and return a response; it does not need events or an event log.

This backend fragment assumes your application implements and registers the account service. That interface is application-owned, not an Arc API:

using Cratis.Arc.Commands.ModelBound;
using Cratis.Concepts;
public record AccountName(string Value) : ConceptAs<string>(Value)
{
public static readonly AccountName NotSet = new(string.Empty);
}
public record AccountBalance(decimal Value) : ConceptAs<decimal>(Value)
{
public static readonly AccountBalance NotSet = new(0m);
}
public interface IAccountService
{
Task<Guid> Open(AccountName name, AccountBalance initialBalance);
}
[Command]
public record OpenDebitAccount(AccountName Name, AccountBalance InitialBalance)
{
public Task<Guid> Handle(IAccountService accounts) =>
accounts.Open(Name, InitialBalance);
}

View C# snippet source on GitHub

The named concepts keep account names and balances distinct in backend signatures. Their generated client properties use the underlying primitive — here string and number — so the form does not need to construct concept objects. Keep each concept in its own file when organizing your application.

After generation, use the proxy from a React component. This consumer fragment assumes its generated import path is ./generated/OpenDebitAccount:

import { OpenDebitAccount } from './generated/OpenDebitAccount';
export function OpenAccountButton() {
const [command] = OpenDebitAccount.use({
name: 'Primary account',
initialBalance: 500
});
async function open() {
const result = await command.execute();
if (result.isSuccess) {
console.log(String(result.response));
}
}
return <button onClick={() => void open()}>Open account</button>;
}

An ordinary Guid/UUID response remains a supported response and maps to Fundamentals Guid. The Chronicle integration adds optional event-sourcing behavior; it does not redefine generic Arc responses.

The command hook returns [command, setValues, clearValues]. Set required content explicitly. The setter edits properties; it does not reset the change-tracking baseline. See data binding.

Both controller-based and model-bound C# endpoints generate proxies; the JVM generates from model-bound @ReadModel query methods only — it has no controller-based discovery. TypeScript’s source generator reads @readModel() classes with static @query() methods; see its proxy generation overview for supported output. Parameterized queries emit a NameParameters interface. Query hooks return tuples, not model objects; read the first tuple element’s .data.

  • Ordinary .use() returns [result, perform, setSorting].
  • Observable enumerable .use() returns [result, setSorting].
  • Observable single-model .use() returns [result].
  • Only enumerable proxies expose .useWithPaging(), .useSuspenseWithPaging(), and, for observables, .useChangeStream().

See query usage for argument positions and the complete tuple matrix. Results expose isPerforming, isReady, isAuthorized, isValid, hasExceptions, and exceptionMessages; they do not expose an error or isLoading property.

Observable proxies subscribe to an emitting backend source. MongoDB observation is one option; Chronicle is another optional integration. A database write does not become a live notification without that source wiring. Both SSE and WebSocket support direct and shared-hub modes.

All generated queries expose .useSuspense(). Suspense handles initial waiting and selected failures, not every unsuccessful result: keep validation/readiness handling in the component. Read Suspense limitations before adding an error boundary or retry control.