163 lines
4.6 KiB
Markdown
163 lines
4.6 KiB
Markdown
# 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 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.<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.
|