This commit is contained in:
MaKarin
2026-04-06 12:50:46 +03:00
commit 73ddb1a948
155 changed files with 26688 additions and 0 deletions

248
domain/dsl-spec.md Normal file
View File

@@ -0,0 +1,248 @@
# DSL Language Specification
This document describes the DSL system used to specify fullstack CRUD applications.
`domain/*.api.dsl` is the single source of truth for the entire domain model and API
contract. It drives Prisma schema generation, NestJS module generation, and React Admin
resource generation.
`api-summary.json` is a derived artifact generated from the api.dsl to stabilize
LLM-first generation and feed the lightweight validation gate. It must never replace the
DSL as the source of truth. The active prompt corpus that consumes this contract lives in
`prompts/`.
---
# DSL Architecture
## `domain/*.api.dsl`
The api.dsl is the authoritative source of truth for:
- entities and their attributes
- scalar types and enums
- primary keys and foreign keys
- database-level constraints (required, unique, default)
- relations between entities
- DTO shapes per operation (Create, Update, Read, List)
- nullability and requiredness of each DTO attribute per operation
- HTTP methods and paths for each endpoint
- endpoint names and groupings
- pagination request/response contracts
The api.dsl drives: Prisma schema, NestJS controller/service/DTO generation,
and React Admin resource generation.
Constraint: every `map Entity.field` or `sync Entity.field` reference in `domain/*.api.dsl`
must resolve to an entity and field defined within the same api.dsl file.
## Optional extension mechanism
```text
overrides/
api-overrides.dsl
ui-overrides.dsl
```
Override rules:
- Overrides are optional.
- The generator must work without them.
- Overrides may refine derived API or UI behavior, but they must not duplicate or redefine
entities, attributes, primary keys, foreign keys, relations, or enums.
---
# DSL Grammar Concepts
## entity
An **entity** is a domain object that becomes a database table and a first-class resource in the backend and frontend.
```
entity Equipment {
attribute id { type uuid; key primary; }
attribute name { type string; is required; }
}
```
- **Domain:** Defines the canonical model; one entity = one Prisma model, one NestJS module, one React Admin resource.
- **Naming:** PascalCase (e.g. `Equipment`, `EquipmentType`, `RepairOrder`).
---
## attribute
An **attribute** is a field of an entity. It has a type and optional modifiers.
```
attribute name {
description "Наименование";
type string;
is required;
is unique;
}
```
**Modifiers:**
- `type` — required; one of: `string`, `uuid`, `integer`, `decimal`, `date`, `text`, `boolean`, `number`, or an enum name.
- `key primary` — this attribute is the primary key.
- `key foreign { relates Entity.field }` — foreign key to another entity's field.
- `is required` — non-nullable.
- `is unique` — unique constraint.
- `is nullable` — explicitly nullable.
- `default Value` — default value (for enums or literals).
- `description "..."` — human-readable description.
- `label "..."` — display label for UI.
---
## enum
An **enum** defines a fixed set of values. Used for attributes that can only take one of these values.
```
enum EquipmentStatus {
value Active { label "В эксплуатации"; }
value Repair { label "В ремонте"; }
}
```
- **value** — identifier used in data and code.
- **label** — optional display label for UI.
---
## primary key
Exactly one attribute per entity must be marked as primary key.
```
attribute id {
type uuid;
key primary;
}
```
Or for a natural key:
```
attribute code {
type string;
key primary;
is required;
is unique;
}
```
---
## foreign key
A **foreign key** links to another entity's primary key. The attribute type must match the referenced primary key type.
```
attribute equipmentTypeCode {
type string;
key foreign {
relates EquipmentType.code;
}
is required;
}
```
- `relates Entity.attribute` — references `Entity`'s `attribute` (must be primary key).
- FK type must equal referenced PK type (e.g. `string``EquipmentType.code`, `uuid``Equipment.id`).
---
## required
- **is required** — attribute is non-nullable in the domain model and drives requiredness in derived DTO/API/UI artifacts.
- Absence of `is required` means the attribute is optional (nullable).
---
## default
- **default Value** — applied when no value is provided (e.g. enum defaults like `default Active`).
- Value must exist in the enum when the attribute type is an enum.
---
# DSL → System Component Mapping
## DSL → Prisma
| DSL Concept | Prisma Result |
| ------------ | --------------------------- |
| entity | model |
| attribute | field |
| enum | enum |
| key primary | @id or @id @default(uuid()) |
| key foreign | relation + references |
| type string | String |
| type uuid | String @id @default(uuid()) |
| type integer | Int |
| type number | Float |
| type decimal | Decimal |
| type date | DateTime |
| type text | String |
| type boolean | Boolean |
---
## DSL → NestJS
| DSL Concept | NestJS Result |
| --------------------------- | ------------------------------------- |
| entity | One module (e.g. equipment.module.ts) |
| entity | Controller with CRUD endpoints |
| entity | Service with Prisma CRUD |
| entity + attribute metadata | create-{entity}.dto.ts |
| entity + attribute metadata | update-{entity}.dto.ts |
| entity + attribute metadata | Response DTO / API shape |
API paths are derived from entity name: PascalCase → kebab-case, pluralized (e.g. `Equipment``/equipment`, `RepairOrder``/repair-orders`).
---
## DSL → React Admin
| DSL Concept | React Admin Result |
| -------------------- | ----------------------------------- |
| entity | Resource (name = kebab-case plural) |
| attribute | Form field / column |
| type string | TextInput, TextField |
| type integer/decimal | NumberInput, NumberField |
| type number | NumberInput, NumberField |
| type date | DateInput, DateField |
| type boolean | BooleanInput, BooleanField |
| enum | SelectInput with choices |
| foreign key | ReferenceInput, ReferenceField |
---
# API DSL Layer Mapping
DTO shapes and endpoint contracts are declared in `domain/*.api.dsl`. The api.dsl
is the authoritative source for:
- **Create DTO** — declared as `dto DTO.<Entity>Create` in api.dsl. Must not include
server-generated primary keys (e.g. no `id` for uuid PKs). Required/nullable per field
is explicit in the api.dsl, not inferred.
- **Update DTO** — declared as `dto DTO.<Entity>Update` in api.dsl. All fields are
typically nullable for partial update semantics.
- **API response shape** — declared as `dto DTO.<Entity>` in api.dsl. Must expose
React Admin-compatible `id` field.
- **UI field mapping** — derived from the DTO shapes in api.dsl, not from domain
attributes directly. The Create form uses `DTO.<Entity>Create` fields; the Edit form
uses `DTO.<Entity>Update` fields; List and Show use `DTO.<Entity>` fields.

367
domain/toir.api.dsl Normal file
View File

@@ -0,0 +1,367 @@
dto DTO.Equipment {
description "Полный response-объект для единицы оборудования";
attribute id {
type uuid;
map Equipment.id;
}
attribute name {
type string;
description "Название оборудования";
map Equipment.name;
}
attribute serialNumber {
type string;
description "Заводской (серийный) номер";
map Equipment.serialNumber;
}
attribute dateOfInspection {
type date;
is nullable;
description "Дата поверки";
map Equipment.dateOfInspection;
}
attribute commissionedAt {
type date;
is nullable;
description "Дата изготовления";
map Equipment.commissionedAt;
}
attribute status {
type EquipmentStatus;
description "Текущий статус";
map Equipment.status;
}
}
dto DTO.EquipmentCreate {
description "Тело запроса на создание единицы оборудования";
attribute name {
type string;
description "Название оборудования";
is required;
map Equipment.name;
}
attribute serialNumber {
type string;
description "Заводской (серийный) номер";
is required;
map Equipment.serialNumber;
}
attribute dateOfInspection {
type date;
is nullable;
description "Дата поверки";
map Equipment.dateOfInspection;
}
attribute commissionedAt {
type date;
is nullable;
description "Дата изготовления";
map Equipment.commissionedAt;
}
attribute status {
type EquipmentStatus;
description "Текущий статус";
is required;
map Equipment.status;
}
}
dto DTO.EquipmentUpdate {
description "Тело запроса на обновление единицы оборудования";
attribute name {
type string;
description "Название оборудования";
is nullable;
map Equipment.name;
}
attribute serialNumber {
type string;
description "Заводской (серийный) номер";
is nullable;
map Equipment.serialNumber;
}
attribute dateOfInspection {
type date;
is nullable;
description "Дата поверки";
map Equipment.dateOfInspection;
}
attribute commissionedAt {
type date;
is nullable;
description "Дата изготовления";
map Equipment.commissionedAt;
}
attribute status {
type EquipmentStatus;
description "Текущий статус";
is nullable;
map Equipment.status;
}
}
dto DTO.EquipmentListRequest {
description "Запрос для постраничного получения списка оборудования с фильтрацией";
attribute page {
type DTO.PageRequest;
}
attribute filterName {
type string;
is nullable;
}
attribute filterSerialNumber {
type string;
is nullable;
}
attribute filterStatus {
type EquipmentStatus;
is nullable;
}
}
dto DTO.EquipmentListResponse {
description "Ответ с постраничным списком оборудования и метаданными";
attribute content {
type DTO.Equipment[];
}
attribute pageInfo {
type DTO.PageInfo;
}
}
dto DTO.ChangeEquipmentStatus {
description "Полный response-объект для документа изменения статуса";
attribute equipmentId {
type Equipment;
is nullable;
map ChangeEquipmentStatus.equipmentId;
}
attribute newStatus {
type EquipmentStatus;
description "Новый статус";
map ChangeEquipmentStatus.newStatus;
}
attribute number {
type string;
description "Номер";
is nullable;
map ChangeEquipmentStatus.number;
}
attribute date {
type date;
description "Дата изменения статуса";
map ChangeEquipmentStatus.date;
}
attribute responsible {
type string;
description "Ответственный";
is nullable;
map ChangeEquipmentStatus.responsible;
}
}
dto DTO.ChangeEquipmentStatusCreate {
description "Тело запроса на создание документа изменения статуса";
attribute equipmentId {
type Equipment;
is nullable;
map ChangeEquipmentStatus.equipmentId;
}
attribute newStatus {
type EquipmentStatus;
description "Новый статус";
is required;
map ChangeEquipmentStatus.newStatus;
}
attribute number {
type string;
description "Номер";
is nullable;
map ChangeEquipmentStatus.number;
}
attribute date {
type date;
description "Дата изменения статуса";
is required;
map ChangeEquipmentStatus.date;
}
attribute responsible {
type string;
description "Ответственный";
is nullable;
map ChangeEquipmentStatus.responsible;
}
}
dto DTO.ChangeEquipmentStatusUpdate {
description "Тело запроса на обновление документа изменения статуса";
attribute equipmentId {
type Equipment;
is nullable;
map ChangeEquipmentStatus.equipmentId;
}
attribute newStatus {
type EquipmentStatus;
description "Новый статус";
is nullable;
map ChangeEquipmentStatus.newStatus;
}
attribute number {
type string;
description "Номер";
is nullable;
map ChangeEquipmentStatus.number;
}
attribute date {
type date;
description "Дата изменения статуса";
is nullable;
map ChangeEquipmentStatus.date;
}
attribute responsible {
type string;
description "Ответственный";
is nullable;
map ChangeEquipmentStatus.responsible;
}
}
dto DTO.ChangeEquipmentStatusListRequest {
description "Запрос для постраничного получения списка документов изменения статуса с фильтрацией";
attribute page {
type DTO.PageRequest;
}
attribute filterEquipmentId {
type uuid;
is nullable;
}
attribute filterNumber {
type string;
is nullable;
}
attribute filterDate {
type date;
is nullable;
}
attribute filterResponsible {
type string;
is nullable;
}
}
dto DTO.ChangeEquipmentStatusListResponse {
description "Ответ с постраничным списком документов изменения статуса и метаданными";
attribute content {
type DTO.ChangeEquipmentStatus[];
}
attribute pageInfo {
type DTO.PageInfo;
}
}
api API.Equipment {
description "API управления справочником оборудования";
endpoint listEquipment {
label "POST /equipment/page";
description "Постраничный список оборудования с фильтрацией";
attribute request {
type DTO.EquipmentListRequest;
}
attribute response {
type DTO.EquipmentListResponse;
}
}
endpoint getEquipment {
label "GET /equipment/{id}";
description "Получить единицу оборудования по идентификатору";
attribute id {
type uuid;
}
attribute response {
type DTO.Equipment;
}
}
endpoint createEquipment {
label "POST /equipment";
description "Создать новую единицу оборудования";
attribute request {
type DTO.EquipmentCreate;
}
}
endpoint updateEquipment {
label "PUT /equipment/{id}";
description "Обновить данные единицы оборудования";
attribute id {
type uuid;
}
attribute request {
type DTO.EquipmentUpdate;
}
}
endpoint deleteEquipment {
label "DELETE /equipment/{id}";
description "Удалить единицу оборудования";
attribute id {
type uuid;
}
}
}
api API.EquipmentStatusChange {
description "API управления документами изменения статуса оборудования";
endpoint listStatusChanges {
label "POST /status-changes/page";
description "Постраничный список документов изменения статуса с фильтрацией";
attribute request {
type DTO.ChangeEquipmentStatusListRequest;
}
attribute response {
type DTO.ChangeEquipmentStatusListResponse;
}
}
endpoint getStatusChange {
label "GET /status-changes/{id}";
description "Получить документ изменения статуса по идентификатору";
attribute id {
type uuid;
}
attribute response {
type DTO.ChangeEquipmentStatus;
}
}
endpoint createStatusChange {
label "POST /status-changes";
description "Создать документ изменения статуса оборудования";
attribute request {
type DTO.ChangeEquipmentStatusCreate;
}
}
endpoint updateStatusChange {
labelо "PUT /status-changes/{id}";
description "Обновить документ изменения статуса";
attribute id {
type uuid;
}
attribute request {
type DTO.ChangeEquipmentStatusUpdate;
}
}
endpoint deleteStatusChange {
label "DELETE /status-changes/{id}";
description "Удалить документ изменения статуса";
attribute id {
type uuid;
}
}
}