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.
Files and folders
Section titled “Files and folders”| Backend artifact | Generated file |
|---|---|
@command() class RegisterAuthor in Features/Authors/Registration/Registration.ts | Features/Authors/Registration/RegisterAuthor.proxy.ts |
@readModel() class Author in Features/Authors/Listing/Listing.ts | Features/Authors/Listing/Author.proxy.ts, the model |
@query() method allAuthors on Author | Features/Authors/Listing/AllAuthors.proxy.ts, named after the method in PascalCase |
A @field model used by a command, query, or identity provider | A model file in its own namespace folder |
| Separate output folder only | index.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.
Commands
Section titled “Commands”For each command the file holds an interface, a validator when the command has client-safe rules, and the command class:
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'];| Member | Comes from |
|---|---|
I<Command> interface | Every @field property, optional in the interface so you can fill it gradually |
<Command>Validator | The literal, unconditional rules of the command’s validators and of concept validators such as AuthorNameValidator; see Validation rules |
| Base class | Command<IRegisterAuthor> when handle() returns nothing for the client; Command<IRegisterTask, Guid> when it returns a value, here the Tasks sample’s TaskId |
route | The server’s convention route, using the route alignment options |
roles | @roles(...) on the command, for display decisions only |
treatWarningsAsErrors | @command({ treatWarningsAsErrors: true }) |
| Properties | A 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.
Queries
Section titled “Queries”Each query method becomes a class. A query with arguments also gets a parameters interface, named <Query>Parameters with no I prefix:
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[] = [];| Member | Meaning |
|---|---|
| Base class | QueryFor<TResult, TParameters> for a snapshot, ObservableQueryFor<TResult, TParameters> when the method returns an observable source |
queryName | The fully qualified name the server uses for hub subscriptions |
defaultValue | What result.data holds before the first answer: [] for a list, {} as TModel for a single model |
sortBy | For 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, requiredRequestParameters | The arguments, so the client can wait until required ones are set |
validation | A 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.
Hook signatures
Section titled “Hook signatures”result is a QueryResultWithState<TResult> in every row. args appears only for queries with arguments.
| Query shape | Hook | Returns |
|---|---|---|
| Snapshot, single result | use(args?), useSuspense(args?) | [result, perform, setSorting] |
| Snapshot, list | use(args?, sorting?), useSuspense(args?, sorting?) | [result, perform, setSorting] |
| Snapshot, list | useWithPaging(pageSize, args?, sorting?), useSuspenseWithPaging(...) | [result, perform, setSorting, setPage, setPageSize] |
| Observable, single result | use(args?), useSuspense(args?) | [result] |
| Observable, list | use(args?, sorting?), useSuspense(args?, sorting?) | [result, setSorting] |
| Observable, list | useWithPaging(pageSize, args?, sorting?), useSuspenseWithPaging(...) | [result, setSorting, setPage, setPageSize] |
| Observable, list | useChangeStream(args?, getKey?, sorting?) | ChangeSet<TModel> with added, replaced, and removed |
| Any | when(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.
Models
Section titled “Models”A read model or nested @field class becomes a class with the same @field declarations, so the client deserializes JSON into real types:
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.