Skip to content

What the generator writes

This page describes the generated files so you can read them, import from them, and predict what changes when the backend changes. Never edit them: the next run replaces any file that carries the @generated by Cratis header. The excerpts come from the Library and Tasks samples.

Backend artifactGenerated file
@command() class RegisterAuthor in Features/Authors/Registration/Registration.tsFeatures/Authors/Registration/RegisterAuthor.proxy.ts
@readModel() class Author in Features/Authors/Listing/Listing.tsFeatures/Authors/Listing/Author.proxy.ts, the model
@query() method allAuthors on AuthorFeatures/Authors/Listing/AllAuthors.proxy.ts, named after the method in PascalCase
A @field model used by a command, query, or identity providerA model file in its own namespace folder
Separate output folder onlyindex.ts, exporting generated files; no barrels are emitted in co-located mode

Use --artifacts Features --output Features --use-proxy-file-suffix (absolute paths in a script) for the co-located layout. The suffix is required when the output is the artifacts folder or one of its ancestors, or a nested folder that contains backend artifacts (see Configuration); with a dedicated output folder it is optional and files can end in .ts. Namespaces come from the discovery folder, or from an explicit namespace option, prefixed with --root-namespace when you set one. If the namespace or skipped segments change the generated path, check where it lands before importing it.

For each command the file holds an interface, a validator when the command has client-safe rules, and the command class:

Authors/Registration/RegisterAuthor.proxy.ts (excerpt)
export interface IRegisterAuthor {
id?: Guid;
name?: string;
}
export class RegisterAuthorValidator extends CommandValidator<IRegisterAuthor> {
constructor() {
super();
this.ruleFor(c => c.name).notEmpty().withMessage('An author name is required');
this.ruleFor(c => c.name).maxLength(100).withMessage('An author name cannot exceed 100 characters');
}
}
export class RegisterAuthor extends Command<IRegisterAuthor> implements IRegisterAuthor {
readonly route: string = '/api/authors/registration/register-author';
readonly validation: CommandValidator = new RegisterAuthorValidator();
readonly treatWarningsAsErrors: boolean = false;
readonly roles: string[] = ['Librarian'];
MemberComes from
I<Command> interfaceEvery @field property, optional in the interface so you can fill it gradually
<Command>ValidatorThe literal, unconditional rules of the command’s validators and of concept validators such as AuthorNameValidator; see Validation rules
Base classCommand<IRegisterAuthor> when handle() returns nothing for the client; Command<IRegisterTask, Guid> when it returns a value, here the Tasks sample’s TaskId
routeThe server’s convention route, using the route alignment options
roles@roles(...) on the command, for display decisions only
treatWarningsAsErrors@command({ treatWarningsAsErrors: true })
PropertiesA getter and setter per field, with change tracking
static use(initialValues?)Returns [command, setValues, clearValues] for React

Values that handle() returns for the server, such as Chronicle events or command operations, are not part of the client response. Type mapping lists each return shape.

Each query method becomes a class. A query with arguments also gets a parameters interface, named <Query>Parameters with no I prefix:

Books/Listing/BooksForAuthor.proxy.ts (excerpt)
export interface BooksForAuthorParameters {
authorId: Guid;
}
export class BooksForAuthor extends ObservableQueryFor<Book[], BooksForAuthorParameters> {
readonly route: string = '/api/books/listing/books-for-author';
readonly queryName: string = 'Books.Listing.Book.booksForAuthor';
readonly treatWarningsAsErrors: boolean = false;
readonly roles: string[] = [];
readonly defaultValue: Book[] = [];
MemberMeaning
Base classQueryFor<TResult, TParameters> for a snapshot, ObservableQueryFor<TResult, TParameters> when the method returns an observable source
queryNameThe fully qualified name the server uses for hub subscriptions
defaultValueWhat result.data holds before the first answer: [] for a list, {} as TModel for a single model
sortByFor list results, one sort helper per model field, as a static and an instance property; complex-field helpers are deprecated for removal in the next major release
parameterDescriptors, requiredRequestParametersThe arguments, so the client can wait until required ones are set
validationA QueryValidator when the query’s arguments have client-safe rules

A query without arguments has no parameters interface, and its hooks take sorting as the first argument.

result is a QueryResultWithState<TResult> in every row. args appears only for queries with arguments.

Query shapeHookReturns
Snapshot, single resultuse(args?), useSuspense(args?)[result, perform, setSorting]
Snapshot, listuse(args?, sorting?), useSuspense(args?, sorting?)[result, perform, setSorting]
Snapshot, listuseWithPaging(pageSize, args?, sorting?), useSuspenseWithPaging(...)[result, perform, setSorting, setPage, setPageSize]
Observable, single resultuse(args?), useSuspense(args?)[result]
Observable, listuse(args?, sorting?), useSuspense(args?, sorting?)[result, setSorting]
Observable, listuseWithPaging(pageSize, args?, sorting?), useSuspenseWithPaging(...)[result, setSorting, setPage, setPageSize]
Observable, listuseChangeStream(args?, getKey?, sorting?)ChangeSet<TModel> with added, replaced, and removed
Anywhen(condition)A builder with the same hooks, enabled only while condition is true

Only list results get paging, sorting helpers, and change streams. That is a client API rule; the server decides how it pages. See Paging.

A read model or nested @field class becomes a class with the same @field declarations, so the client deserializes JSON into real types:

Authors/Listing/Author.proxy.ts (excerpt)
export class Author {
@field(Guid)
id!: Guid;
@field(String)
name!: string;
}

The server declares @field(AuthorId) id and @field(AuthorName) name. Concepts arrive as their underlying types, here Guid and string, because the wire carries only the value. With --emit-interfaces the generator writes interfaces instead, which carry no runtime metadata. The identity provider’s detailsType is generated the same way, for useIdentity.