Compare commits
2 Commits
aab7bfa691
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8094c12de | ||
|
|
a4f042a57c |
@@ -104,13 +104,59 @@ Type and decorator rules:
|
|||||||
| `decimal` | `string` | `@IsString()` | serialize with Prisma Decimal |
|
| `decimal` | `string` | `@IsString()` | serialize with Prisma Decimal |
|
||||||
| `date` | `string` | `@IsString()` | serialize as ISO string |
|
| `date` | `string` | `@IsString()` | serialize as ISO string |
|
||||||
| `boolean` | `boolean` | `@IsBoolean()` | |
|
| `boolean` | `boolean` | `@IsBoolean()` | |
|
||||||
| enum name | `string` | `@IsEnum(EnumName)` | |
|
| enum name | `EnumName` (imported from `../../enums/<kebab>.enum`) | `@IsEnum(EnumName)` | Do not use `string` |
|
||||||
|
|
||||||
Nullability rules:
|
Nullability rules:
|
||||||
|
|
||||||
- every field that is not `is required` gets `@IsOptional()` before the type decorator
|
- every field that is not `is required` gets `@IsOptional()` before the type decorator
|
||||||
- every generated DTO imports from `'class-validator'`
|
- every generated DTO imports from `'class-validator'`
|
||||||
|
|
||||||
|
## Shared Types
|
||||||
|
|
||||||
|
Shared TypeScript types are generated by the `shared-types` stage BEFORE
|
||||||
|
`nest-entities` runs. Per-entity modules MUST import them; they must NEVER
|
||||||
|
redefine them.
|
||||||
|
|
||||||
|
Zones:
|
||||||
|
|
||||||
|
- `server/src/enums/<kebab-enum-name>.enum.ts` — one file per DSL enum
|
||||||
|
- `server/src/shared/pagination.ts` — `PaginatedResponse<T>` and `ListQueryParams`
|
||||||
|
- `server/src/shared/index.ts` — barrel for shared types
|
||||||
|
|
||||||
|
Import conventions depend on the importing file's depth:
|
||||||
|
|
||||||
|
- From a module/controller/service file at `server/src/modules/<kebab>/<file>.ts`:
|
||||||
|
```ts
|
||||||
|
import { EquipmentStatus } from '../../enums/equipment-status.enum';
|
||||||
|
import { PaginatedResponse, ListQueryParams } from '../../shared/pagination';
|
||||||
|
```
|
||||||
|
|
||||||
|
- From a DTO file at `server/src/modules/<kebab>/dto/<file>.ts` (one level deeper):
|
||||||
|
```ts
|
||||||
|
import { EquipmentStatus } from '../../../enums/equipment-status.enum';
|
||||||
|
import { PaginatedResponse, ListQueryParams } from '../../../shared/pagination';
|
||||||
|
```
|
||||||
|
|
||||||
|
Enum-typed DTO fields MUST be declared with the actual enum type, NOT
|
||||||
|
`string`. The enum is imported from the shared file above. `@IsEnum(EnumName)`
|
||||||
|
still applies at runtime for validation. Because the DTO type matches the
|
||||||
|
Prisma-generated enum type by name and by string values, service code can
|
||||||
|
pass DTOs into Prisma without `as EnumName` casts.
|
||||||
|
|
||||||
|
## 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
|
## Controller Contract
|
||||||
|
|
||||||
- Apply `@UseGuards(JwtAuthGuard, RolesGuard)` at controller class level.
|
- Apply `@UseGuards(JwtAuthGuard, RolesGuard)` at controller class level.
|
||||||
@@ -134,6 +180,31 @@ Nullability rules:
|
|||||||
- call `$connect()`
|
- call `$connect()`
|
||||||
- do not add `beforeExit`
|
- 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:
|
List endpoint requirements:
|
||||||
|
|
||||||
- accept React Admin query params: `_start`, `_end`, `_sort`, `_order`, `q`
|
- accept React Admin query params: `_start`, `_end`, `_sort`, `_order`, `q`
|
||||||
@@ -156,6 +227,26 @@ Decimal and date handling:
|
|||||||
- `date` writes: `new Date(value)`
|
- `date` writes: `new Date(value)`
|
||||||
- `date` reads: `.toISOString()`
|
- `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
|
## Natural-Key Rules
|
||||||
|
|
||||||
For entities whose physical primary key is not `id`:
|
For entities whose physical primary key is not `id`:
|
||||||
|
|||||||
92
prompts/shared-types-rules.md
Normal file
92
prompts/shared-types-rules.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Shared Types Generation Rules
|
||||||
|
|
||||||
|
You generate the shared TypeScript types consumed by all per-entity NestJS
|
||||||
|
modules in the runner-produced project. You run once per job, between the
|
||||||
|
Prisma schema stage and the per-entity NestJS stage.
|
||||||
|
|
||||||
|
## Write zones
|
||||||
|
|
||||||
|
You MAY write files under these two prefixes, and NOWHERE else:
|
||||||
|
|
||||||
|
- `server/src/enums/`
|
||||||
|
- `server/src/shared/`
|
||||||
|
|
||||||
|
Any file you emit outside these prefixes will be dropped by the runner.
|
||||||
|
|
||||||
|
## Required outputs
|
||||||
|
|
||||||
|
1. **One file per enum** in the frozen contract, at
|
||||||
|
`server/src/enums/<kebab-name>.enum.ts`. Kebab-case is derived from the
|
||||||
|
PascalCase enum name (e.g. `EquipmentStatus` → `equipment-status`).
|
||||||
|
|
||||||
|
Each file exports a `string` TypeScript enum whose member names and values
|
||||||
|
are identical to the DSL enum value names. Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export enum EquipmentStatus {
|
||||||
|
Active = 'Active',
|
||||||
|
Repair = 'Repair',
|
||||||
|
Reserve = 'Reserve',
|
||||||
|
WriteOff = 'WriteOff',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
String-valued enums are required. Do NOT emit numeric enums. Do NOT use
|
||||||
|
`const enum`. Do NOT add helper functions, labels, descriptions, or
|
||||||
|
metadata; the enum body is the only export.
|
||||||
|
|
||||||
|
2. **`server/src/shared/pagination.ts`** — pagination contract used by list
|
||||||
|
endpoints and list service methods. Exact shape:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
data: T[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListQueryParams {
|
||||||
|
_start?: string;
|
||||||
|
_end?: string;
|
||||||
|
_sort?: string;
|
||||||
|
_order?: 'ASC' | 'DESC' | 'asc' | 'desc';
|
||||||
|
q?: string;
|
||||||
|
[key: string]: string | string[] | undefined;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ListQueryParams` MUST include the string index signature — React Admin
|
||||||
|
passes arbitrary filter params as query strings, and the per-entity
|
||||||
|
services rely on that generic access pattern.
|
||||||
|
|
||||||
|
3. **`server/src/shared/index.ts`** — barrel file re-exporting pagination:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from './pagination';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do NOT emit DTOs, module files, Prisma schema, auth files, controllers, or
|
||||||
|
services. Those belong to other stages and will be dropped.
|
||||||
|
- Do NOT import from `@nestjs/*`, `@prisma/client`, or any runtime package.
|
||||||
|
Shared types are pure TypeScript with no runtime dependencies.
|
||||||
|
- Do NOT inline enum labels, descriptions, or i18n strings in the enum files.
|
||||||
|
- Do NOT emit `.d.ts` files. Use normal `.ts` files.
|
||||||
|
|
||||||
|
## Response format
|
||||||
|
|
||||||
|
Respond with a single JSON object and nothing else — no prose, no markdown
|
||||||
|
commentary, no leading or trailing text:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{ "path": "server/src/enums/<kebab>.enum.ts", "content": "..." },
|
||||||
|
{ "path": "server/src/shared/pagination.ts", "content": "..." },
|
||||||
|
{ "path": "server/src/shared/index.ts", "content": "..." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you must return additional enum files, append them to the `files` array.
|
||||||
|
All paths MUST start with one of the two allowed prefixes.
|
||||||
Reference in New Issue
Block a user