prompts: add shared-types-rules; expand backend-rules
Adds the new shared-types stage system prompt for the runner exporter pipeline (rules for emitting enum files plus PaginatedResponse and ListQueryParams under server/src/enums and server/src/shared). Updates backend-rules.md with the iterative fixes that accumulated in the runner during recent generation runs: composite key Prisma syntax, auth import paths under guards/decorators subfolders, Prisma relation connect syntax for FK writes, dynamic sort type assertion, and an initial Shared Enum Files section that the next prompt task will replace with a Shared Types section pointing at the new shared-types zone. These prompts are the source of truth for runner/toir-fullstack-exporter which copies them in via scripts/sync-context.mjs at build time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,46 @@ Nullability rules:
|
||||
- every field that is not `is required` gets `@IsOptional()` before the type decorator
|
||||
- every generated DTO imports from `'class-validator'`
|
||||
|
||||
## Shared Enum Files
|
||||
|
||||
Enums referenced by DTOs across multiple modules MUST be generated as standalone files at:
|
||||
|
||||
```
|
||||
server/src/enums/<EnumName>.enum.ts
|
||||
```
|
||||
|
||||
Example for `EquipmentStatus`:
|
||||
```ts
|
||||
// server/src/enums/equipment-status.enum.ts
|
||||
export enum EquipmentStatus {
|
||||
Active = 'Active',
|
||||
Repair = 'Repair',
|
||||
Reserve = 'Reserve',
|
||||
WriteOff = 'WriteOff',
|
||||
}
|
||||
```
|
||||
|
||||
DTO files import the enum with path `../../enums/<enum-name>.enum` (two levels up from `modules/<kebab>/dto/`):
|
||||
```ts
|
||||
import { EquipmentStatus } from '../../enums/equipment-status.enum';
|
||||
```
|
||||
|
||||
Do NOT inline enum values into DTOs. Do NOT put enum files inside the module folder.
|
||||
|
||||
## Auth Import Paths
|
||||
|
||||
All auth utilities live under `src/auth/` with the following **exact** import paths (relative to a module file at `src/modules/<kebab>/`):
|
||||
|
||||
| Symbol | Import path |
|
||||
|--------|-------------|
|
||||
| `JwtAuthGuard` | `../../auth/guards/jwt-auth.guard` |
|
||||
| `RolesGuard` | `../../auth/guards/roles.guard` |
|
||||
| `Roles` | `../../auth/decorators/roles.decorator` |
|
||||
| `Public` | `../../auth/decorators/public.decorator` |
|
||||
| `PrismaService` | `../../prisma/prisma.service` |
|
||||
|
||||
Do NOT use `../../auth/jwt-auth.guard`, `../../auth/roles.guard`, or `../../auth/roles.decorator` — those paths do not exist.
|
||||
|
||||
## Controller Contract
|
||||
|
||||
- Apply `@UseGuards(JwtAuthGuard, RolesGuard)` at controller class level.
|
||||
@@ -134,6 +174,31 @@ Nullability rules:
|
||||
- call `$connect()`
|
||||
- do not add `beforeExit`
|
||||
|
||||
### Prisma Relation Writes
|
||||
|
||||
When a DTO field is a foreign key (e.g. `equipmentId: string`), **never** assign it directly on the Prisma data object. Instead, use the `connect` syntax targeting the relation field name (without the `Id` suffix):
|
||||
|
||||
```ts
|
||||
// WRONG — Prisma rejects scalar FK assignments on relation inputs
|
||||
data.equipmentId = dto.equipmentId;
|
||||
|
||||
// CORRECT — use connect on the relation field
|
||||
data.equipment = { connect: { id: dto.equipmentId } };
|
||||
```
|
||||
|
||||
Rule: for any DTO field named `<relation>Id` where `<relation>` is a Prisma relation field, map it as `{ connect: { id: value } }` on the relation name in both create and update payloads.
|
||||
|
||||
### Dynamic Sort Type Assertion
|
||||
|
||||
When building `orderBy` from a runtime sort field, TypeScript requires an explicit cast to avoid `TS7053`. Always use:
|
||||
|
||||
```ts
|
||||
const orderBy: Record<string, 'asc' | 'desc'> = {};
|
||||
orderBy[sortField] = query._order === 'DESC' ? 'desc' : 'asc';
|
||||
```
|
||||
|
||||
Do NOT use typed Prisma `OrderByWithRelationInput` objects for dynamic sort — use `Record<string, 'asc' | 'desc'>` and pass it directly to Prisma's `orderBy`.
|
||||
|
||||
List endpoint requirements:
|
||||
|
||||
- accept React Admin query params: `_start`, `_end`, `_sort`, `_order`, `q`
|
||||
@@ -156,6 +221,26 @@ Decimal and date handling:
|
||||
- `date` writes: `new Date(value)`
|
||||
- `date` reads: `.toISOString()`
|
||||
|
||||
## Composite Key Rules
|
||||
|
||||
When an entity's API endpoints use multiple path parameters (e.g. `GET /entity/{fieldA}/{fieldB}`), the entity has a **composite primary key** — there is NO auto-generated `id` field.
|
||||
|
||||
In this case:
|
||||
- The Prisma model uses `@@id([fieldA, fieldB])` — never `id String @id @default(uuid())`
|
||||
- Prisma's `WhereUniqueInput` accepts `{ fieldA_fieldB: { fieldA, fieldB } }` (underscore-joined)
|
||||
- The NestJS service uses the composite key object, NOT `{ id }`:
|
||||
|
||||
```ts
|
||||
// WRONG — id does not exist on composite-key entities
|
||||
where: { id }
|
||||
|
||||
// CORRECT — use the Prisma compound unique input
|
||||
where: { equipmentId_newStatus: { equipmentId, newStatus } }
|
||||
```
|
||||
|
||||
- Controller route params extract all key fields, not a single `id`
|
||||
- Responses include all key fields; React Admin requires a virtual `id` field — map it as a concatenation: `id: \`${record.equipmentId}_${record.newStatus}\``
|
||||
|
||||
## Natural-Key Rules
|
||||
|
||||
For entities whose physical primary key is not `id`:
|
||||
|
||||
Reference in New Issue
Block a user