# api.dsl Conventions Grammar and authoring conventions for `domain/*.api.dsl` files. See `domain/toir.api.dsl` as the canonical example. --- ## File location and naming - All api.dsl files live in `domain/`. - Naming: `.api.dsl` (e.g. `toir.api.dsl`). - Multiple api.dsl files are allowed but not required. --- ## Two top-level block types An api.dsl file contains two types of top-level blocks: 1. `dto` — defines the shape of a data transfer object 2. `api` — declares a group of API endpoints for one resource --- ## dto block ``` dto DTO. { description "Human-readable description"; attribute { description "Field description"; type ; is required; // or: is nullable; map .; // links to a field in domain/*.api.dsl } } ``` ### DTO naming convention | DTO name | Purpose | |----------|---------| | `DTO.` | Full response shape (GET by id, list items) | | `DTO.Create` | Create request body | | `DTO.Update` | Update request body (partial — all fields nullable) | | `DTO.ListRequest` | Paginated list request (filters + page) | | `DTO.ListResponse` | Paginated list response (content + page info) | ### Types Same scalar types as the domain DSL: `uuid`, `string`, `text`, `integer`, `decimal`, `date`, `boolean`, or an enum name from `domain/*.api.dsl`. Cross-DTO references: `DTO.` or `DTO.[]`. Standard pagination types (not entity-specific; treated as well-known): `DTO.Filter[]`, `DTO.PageRequest`, `DTO.PageInfo`. ### is required vs is nullable Unlike the domain DSL (where these drive DB constraints), in api.dsl these drive the TypeScript DTO property modifier: - `is required` → `field!: type` in the generated class - `is nullable` → `field?: type` in the generated class - Neither modifier → treated as optional (`field?: type`) ### map directive `map Entity.field` links the DTO attribute to a domain entity field. Rules: - The entity and field must exist in `domain/*.api.dsl`. - The scalar type must match the domain DSL field type (nullability may differ). - Omit `map` only for pagination helper types (`DTO.Filter[]`, `DTO.PageRequest`, etc.) that have no direct entity field counterpart. ### Conflict resolution If the type declared in a `dto` attribute conflicts with the domain DSL field type, the domain DSL is correct. Fix the api.dsl. --- ## api block ``` api API. { description "API group description"; endpoint { label "METHOD /path"; description "Endpoint description"; // For endpoints with a request body: attribute request { type DTO.; } // For endpoints with a response body: attribute response { type DTO.; } // For path parameters: attribute { type ; } } } ``` ### api block naming `API.` (e.g. `API.Equipment`, `API.RepairOrder`) ### endpoint label `label "METHOD /path"` is the authoritative declaration of HTTP verb and route. | Label pattern | NestJS decorator | Notes | |---------------|-----------------|-------| | `"GET /resource/{id}"` | `@Get(':id')` | | | `"POST /resource"` | `@Post()` | Create | | `"POST /resource/page"` | `@Post('page')` | Paginated list (body-based filter) | | `"PUT /resource/{id}"` | `@Put(':id')` | Full or partial update | | `"DELETE /resource/{id}"` | `@Delete(':id')` | | Do not infer HTTP verbs from endpoint names. Always read the `label`. ### Path parameters Declared as a plain `attribute` inside the endpoint block (not wrapped in `request` or `response`): ``` attribute id { type uuid; } ``` ### Standard 5-endpoint CRUD pattern | Endpoint name | Label | Body | |---------------|-------|------| | `lists` | `"POST //page"` | request: `DTO.ListRequest`, response: `DTO.ListResponse` | | `get` | `"GET //{id}"` | path param `id`, response: `DTO.` | | `create` | `"POST /"` | request: `DTO.Create` | | `update` | `"PUT //{id}"` | path param `id`, request: `DTO.Update` | | `delete` | `"DELETE //{id}"` | path param `id` | --- ## Constraints 1. Every `map Entity.field` must resolve to an existing entity + field in `domain/*.api.dsl`. 2. Types in api.dsl must be compatible with domain DSL field types (same scalar type). 3. api.dsl must not define entities or enums. Those belong in `domain/*.api.dsl`. 4. An api.dsl may omit domain entity fields from a DTO (e.g. no PK in Create DTO). It must not add fields that don't exist in the domain model.