(llm-first): context budget, validation, and eval harness, orchestration general-prompt
This commit is contained in:
162
docs/api-dsl-conventions.md
Normal file
162
docs/api-dsl-conventions.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user