Files
toir-automatization/docs/api-dsl-conventions.md
2026-04-06 12:50:46 +03:00

4.6 KiB

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: <project>.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.<Name> {
  description "Human-readable description";

  attribute <fieldName> {
    description "Field description";
    type <type>;
    is required;          // or: is nullable;
    map <Entity>.<field>; // links to a field in domain/*.api.dsl
  }
}

DTO naming convention

DTO name Purpose
DTO.<Entity> Full response shape (GET by id, list items)
DTO.<Entity>Create Create request body
DTO.<Entity>Update Update request body (partial — all fields nullable)
DTO.<Entity>ListRequest Paginated list request (filters + page)
DTO.<Entity>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.<Name> or DTO.<Name>[].

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 requiredfield!: type in the generated class
  • is nullablefield?: 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.<Name> {
  description "API group description";

  endpoint <endpointName> {
    label "METHOD /path";
    description "Endpoint description";

    // For endpoints with a request body:
    attribute request {
      type DTO.<RequestDto>;
    }

    // For endpoints with a response body:
    attribute response {
      type DTO.<ResponseDto>;
    }

    // For path parameters:
    attribute <paramName> {
      type <type>;
    }
  }
}

api block naming

API.<EntityName> (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
list<Entity>s "POST /<path>/page" request: DTO.<Entity>ListRequest, response: DTO.<Entity>ListResponse
get<Entity> "GET /<path>/{id}" path param id, response: DTO.<Entity>
create<Entity> "POST /<path>" request: DTO.<Entity>Create
update<Entity> "PUT /<path>/{id}" path param id, request: DTO.<Entity>Update
delete<Entity> "DELETE /<path>/{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.