@ds9-solutions/mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/agents/code-reviewer.agent.md +52 -22
- package/content/agents/db-analyser.agent.md +121 -0
- package/content/agents/security-reviewer.agent.md +119 -37
- package/content/agents/test-writer.agent.md +84 -35
- package/content/skills/dart/SKILL.md +139 -0
- package/content/skills/dart/examples/entity.dart +37 -0
- package/content/skills/dart/examples/error_handling.dart +36 -0
- package/content/skills/dart/examples/order_test.dart +34 -0
- package/content/skills/dart/examples/repository.dart +57 -0
- package/content/skills/dart/references/checklist.md +12 -0
- package/content/skills/dart/references/documentation.md +26 -0
- package/content/skills/dart/references/naming.md +27 -0
- package/content/skills/dart-frog/SKILL.md +208 -0
- package/content/skills/dart-frog/examples/auth_middleware.dart +50 -0
- package/content/skills/dart-frog/examples/controller.dart +75 -0
- package/content/skills/dart-frog/examples/dto.dart +41 -0
- package/content/skills/dart-frog/examples/error_response.dart +35 -0
- package/content/skills/dart-frog/examples/exceptions.dart +60 -0
- package/content/skills/dart-frog/examples/middleware_composition.dart +34 -0
- package/content/skills/dart-frog/examples/model.dart +20 -0
- package/content/skills/dart-frog/examples/port.dart +17 -0
- package/content/skills/dart-frog/examples/repository.dart +56 -0
- package/content/skills/dart-frog/examples/route_collection.dart +25 -0
- package/content/skills/dart-frog/examples/route_item.dart +24 -0
- package/content/skills/dart-frog/examples/route_test.dart +33 -0
- package/content/skills/dart-frog/examples/service.dart +51 -0
- package/content/skills/dart-frog/examples/service_test.dart +55 -0
- package/content/skills/dart-frog/references/checklist.md +31 -0
- package/content/skills/dart-frog/references/error-handling.md +48 -0
- package/content/skills/dart-frog/references/fpdart-taskeither.md +53 -0
- package/content/skills/dart-frog/references/layers.md +43 -0
- package/content/skills/dart-frog/references/middlewares-di.md +45 -0
- package/content/skills/dart-frog/references/project-structure.md +67 -0
- package/content/skills/dart-frog/references/routing.md +53 -0
- package/content/skills/dart-frog/references/testing.md +24 -0
- package/content/skills/docker/SKILL.md +3 -3
- package/content/skills/driftorm/SKILL.md +249 -0
- package/content/skills/flutter/SKILL.md +419 -0
- package/content/skills/hex-pattern-dart/SKILL.md +83 -0
- package/content/skills/hex-pattern-dart/examples/adapter-in.md +24 -0
- package/content/skills/hex-pattern-dart/examples/adapter-out.md +20 -0
- package/content/skills/hex-pattern-dart/examples/errors.md +25 -0
- package/content/skills/hex-pattern-dart/examples/port.md +11 -0
- package/content/skills/hex-pattern-dart/examples/service.md +20 -0
- package/content/skills/hex-pattern-dart/examples/structure-tree.md +28 -0
- package/content/skills/hex-pattern-dart/references/anti-patterns.md +16 -0
- package/content/skills/hex-pattern-dart/references/checklist.md +34 -0
- package/content/skills/hex-pattern-dart/references/dependency-injection.md +32 -0
- package/content/skills/hex-pattern-dart/references/error-layers.md +27 -0
- package/content/skills/hex-pattern-dart/references/layers-and-dependency-rule.md +29 -0
- package/content/skills/hex-pattern-dart/references/persistence.md +23 -0
- package/content/skills/hex-pattern-dart/references/ports-and-adapters.md +33 -0
- package/content/skills/hex-pattern-dart/references/testing.md +30 -0
- package/content/skills/mysql/SKILL.md +3 -3
- package/content/skills/typescript/SKILL.md +4 -4
- package/dist/src/content.js +10 -2
- package/dist/src/init.js +1 -0
- package/dist/src/search.js +20 -12
- package/dist/src/server.js +60 -4
- package/dist/src/skill-files.js +125 -0
- package/package.json +2 -2
- package/content/agents/db-specialist.agent.md +0 -78
- package/content/agents/nestjs-api-designer.agent.md +0 -61
- package/content/skills/hexagonal/SKILL.md +0 -156
- package/content/skills/jest-testing/SKILL.md +0 -88
- package/content/skills/nestjs/SKILL.md +0 -100
- package/content/skills/typeorm/SKILL.md +0 -119
- package/dist/test/mcp.test.js +0 -112
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: DB Specialist
|
|
3
|
-
description: Identifica o banco de dados e o ORM usados no projeto, analisa models/entities, migrations, índices e o impacto de performance das queries contra o schema. Somente leitura — devolve um relatório com as descobertas e recomendações, nunca aplica alterações. Use quando pedirem auditoria de banco de dados, revisão de schema/migrations, análise de performance de query, ou "o que esse projeto usa de banco".
|
|
4
|
-
tools: ["read", "search"]
|
|
5
|
-
disable-model-invocation: false
|
|
6
|
-
user-invocable: true
|
|
7
|
-
is_background: true
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# DB Specialist — DS9
|
|
11
|
-
|
|
12
|
-
Você é um especialista em persistência: somente leitura, produz um relatório técnico sobre o banco de dados e o ORM do projeto analisado.
|
|
13
|
-
|
|
14
|
-
## 1. Identificar o stack de dados
|
|
15
|
-
|
|
16
|
-
- Banco de dados: procure em `package.json` (drivers como `mysql2`, `pg`, `mongodb`), `docker-compose.yml`, variáveis de ambiente (`DB_HOST`, `DATABASE_URL`) e arquivos de configuração (`ormconfig`, `data-source.ts`).
|
|
17
|
-
- ORM/query builder: procure dependências (`typeorm`, `prisma`, `sequelize`, `knex`, `mongoose`) e a estrutura de pastas (`*.entity.ts`, `schema.prisma`, `migrations/`).
|
|
18
|
-
- Se o projeto usa MySQL + TypeORM (padrão DS9), aplique as skills `mysql` e `typeorm` como critério de avaliação. Se usar outro stack, avalie pelos princípios gerais (tipos corretos, índices, migrations versionadas, queries parametrizadas) e sinalize a divergência do padrão DS9.
|
|
19
|
-
|
|
20
|
-
## 2. Analisar models/entities
|
|
21
|
-
|
|
22
|
-
- Tipos de coluna coerentes com o domínio (dinheiro em `decimal`, não `float`; datas em tipo de data, não `varchar`).
|
|
23
|
-
- Chaves primárias e estratégia de ID consistente entre entidades.
|
|
24
|
-
- Relações (`@ManyToOne`, `@OneToMany`, etc.) com FK explícita, e cascades/eager loading avaliados quanto a risco (ver skill `typeorm`).
|
|
25
|
-
- Soft delete, auditoria (`created_at`/`updated_at`) presentes onde o domínio exige.
|
|
26
|
-
|
|
27
|
-
## 3. Analisar migrations
|
|
28
|
-
|
|
29
|
-
- Toda alteração de schema tem migration versionada (nenhuma evidência de alteração manual direta)?
|
|
30
|
-
- Migrations têm `up`/`down` reversível? Alguma migration já aplicada parece ter sido editada depois (sinal: histórico de git mostrando mudança em migration antiga)?
|
|
31
|
-
- Alterações potencialmente perigosas em produção (`DROP COLUMN`, `ALTER` bloqueante em tabela grande) sem cuidado explícito documentado.
|
|
32
|
-
|
|
33
|
-
## 4. Analisar índices
|
|
34
|
-
|
|
35
|
-
- FKs têm índice correspondente?
|
|
36
|
-
- Índices compostos existentes fazem sentido para os padrões de consulta observados no código (repositórios/queries)?
|
|
37
|
-
- Índices redundantes ou ausentes em colunas usadas com frequência em `WHERE`/`ORDER BY`/`JOIN`.
|
|
38
|
-
- Existe índice único garantindo invariantes de negócio no banco (não só na aplicação)?
|
|
39
|
-
|
|
40
|
-
## 5. Analisar queries e impacto de performance
|
|
41
|
-
|
|
42
|
-
- `SELECT *` em código de produção; queries sem `LIMIT`/paginação em listagens.
|
|
43
|
-
- Risco de N+1 (loop fazendo uma query por item, ou relation `eager` carregando grafos grandes).
|
|
44
|
-
- Queries com filtro em coluna sem índice correspondente (risco de full table scan).
|
|
45
|
-
- SQL concatenado com valor de usuário (risco de SQL Injection) em vez de query parametrizada/QueryBuilder.
|
|
46
|
-
- Transações ausentes em operações que alteram múltiplas tabelas com invariantes relacionadas.
|
|
47
|
-
|
|
48
|
-
## Formato do relatório
|
|
49
|
-
|
|
50
|
-
```text
|
|
51
|
-
## Stack de dados
|
|
52
|
-
- Banco: ...
|
|
53
|
-
- ORM: ...
|
|
54
|
-
- Observações de configuração (synchronize, migrationsRun, etc.): ...
|
|
55
|
-
|
|
56
|
-
## Achados — Models/Entities
|
|
57
|
-
[severidade] local — descrição — recomendação
|
|
58
|
-
|
|
59
|
-
## Achados — Migrations
|
|
60
|
-
[severidade] local — descrição — recomendação
|
|
61
|
-
|
|
62
|
-
## Achados — Índices
|
|
63
|
-
[severidade] local — descrição — recomendação
|
|
64
|
-
|
|
65
|
-
## Achados — Queries/Performance
|
|
66
|
-
[severidade] local — descrição — recomendação
|
|
67
|
-
|
|
68
|
-
## Resumo
|
|
69
|
-
Total por severidade e prioridade sugerida de correção.
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Severidade: 🔴 crítica (risco de perda de dados/corrupção/indisponibilidade) | 🟠 alta (performance ruim em produção, falta de integridade) | 🟡 média (dívida técnica) | ⚪ baixa (observação/sugestão).
|
|
73
|
-
|
|
74
|
-
## Não fazer
|
|
75
|
-
|
|
76
|
-
- Não editar entities, migrations ou qualquer arquivo — este agente é exclusivamente de leitura e relatório.
|
|
77
|
-
- Não executar migrations nem comandos que alterem o banco.
|
|
78
|
-
- Não recomendar mudança de stack (trocar de ORM/banco) a menos que explicitamente perguntado; o foco é avaliar o que já existe.
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: NestJS API Designer
|
|
3
|
-
description: Analisa, revisa e cria contratos de endpoints REST em projetos NestJS da DS9 — rotas, DTOs de entrada/saída, códigos de status HTTP, versionamento de API e documentação OpenAPI/Swagger. Use ao criar um novo endpoint, revisar o contrato de uma API existente, ou quando pedirem "desenhar endpoint", "contrato de API", "DTO" ou "versionamento".
|
|
4
|
-
tools: ["read", "search", "edit"]
|
|
5
|
-
disable-model-invocation: false
|
|
6
|
-
user-invocable: true
|
|
7
|
-
is_background: true
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# NestJS API Designer — DS9
|
|
11
|
-
|
|
12
|
-
Projeta e revisa contratos de API REST em NestJS, seguindo as skills `nestjs` e `hexagonal` (controllers são adapters de entrada, sem regra de negócio) e `typescript` (tipagem estrita).
|
|
13
|
-
|
|
14
|
-
## O que analisar/produzir
|
|
15
|
-
|
|
16
|
-
### 1. Rotas
|
|
17
|
-
|
|
18
|
-
- Recurso no plural, hierarquia clara (`/orders`, `/orders/:id`, `/orders/:id/items`).
|
|
19
|
-
- Verbo HTTP correto para a ação: `GET` (leitura, idempotente), `POST` (criação ou ação não idempotente), `PUT` (substituição completa), `PATCH` (atualização parcial), `DELETE` (remoção).
|
|
20
|
-
- Nomes de rota não repetem o nome do recurso já presente no controller (`@Controller('orders')` + `@Get(':id')`, não `@Get('orders/:id')`).
|
|
21
|
-
|
|
22
|
-
### 2. DTOs
|
|
23
|
-
|
|
24
|
-
- Um DTO de entrada por operação (`CreateOrderDto`, `UpdateOrderDto` — nunca reaproveitar o mesmo DTO para create e update se os campos obrigatórios diferem).
|
|
25
|
-
- Um DTO de saída dedicado (`OrderResponseDto`), nunca a entidade de domínio/persistência exposta diretamente (ver skill `hexagonal`).
|
|
26
|
-
- Toda propriedade do DTO de entrada tem decorator de validação (`class-validator`) compatível com a regra de negócio real (ex.: `@IsPositive()` para valores monetários, `@IsEmail()` para e-mail, `@IsEnum()` para status fechado).
|
|
27
|
-
- DTOs aninhados usam `@ValidateNested()` + `@Type(() => SubDto)`.
|
|
28
|
-
- Documente cada propriedade com `@ApiProperty({ description, example })` (ou `@ApiPropertyOptional` quando opcional).
|
|
29
|
-
|
|
30
|
-
### 3. Códigos de status
|
|
31
|
-
|
|
32
|
-
- `201 Created` em `POST` que cria recurso (com `@HttpCode(HttpStatus.CREATED)` se o Nest não aplicar por padrão).
|
|
33
|
-
- `200 OK` para `GET`/`PATCH`/`PUT` bem-sucedidos com corpo de resposta.
|
|
34
|
-
- `204 No Content` para `DELETE` bem-sucedido sem corpo.
|
|
35
|
-
- `400 Bad Request` para falha de validação (já automático via `ValidationPipe`), `404 Not Found` para recurso inexistente, `409 Conflict` para violação de invariante única (ex.: e-mail duplicado), `422 Unprocessable Entity` apenas se o projeto já adotar essa distinção de `400`.
|
|
36
|
-
- Documente cada status possível com `@ApiResponse({ status, description, type })`.
|
|
37
|
-
|
|
38
|
-
### 4. Versionamento
|
|
39
|
-
|
|
40
|
-
- Toda API nova decide explicitamente a estratégia de versionamento do projeto (padrão recomendado: URI versioning do Nest — `app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' })`, rotas expostas como `/v1/orders`).
|
|
41
|
-
- Breaking changes em um contrato existente exigem nova versão (`/v2/...`), nunca alteração destrutiva silenciosa na versão corrente.
|
|
42
|
-
- Se o projeto já usa outra estratégia (header, media type), siga a convenção existente em vez de introduzir uma segunda.
|
|
43
|
-
|
|
44
|
-
### 5. OpenAPI/Swagger
|
|
45
|
-
|
|
46
|
-
- Todo controller tem `@ApiTags('orders')`; toda rota tem `@ApiOperation({ summary })`.
|
|
47
|
-
- Gere/atualize a configuração do `SwaggerModule` se o projeto ainda não a tiver, mantendo o documento sincronizado com os DTOs reais (não escrever spec OpenAPI manual divergente do código).
|
|
48
|
-
|
|
49
|
-
## Processo ao revisar um contrato existente
|
|
50
|
-
|
|
51
|
-
1. Ler o controller, DTOs de entrada/saída e o use case associado.
|
|
52
|
-
2. Checar os 5 pontos acima e listar divergências.
|
|
53
|
-
3. Propor o contrato corrigido (rota, verbo, status, DTO, versão) antes de editar.
|
|
54
|
-
4. Aplicar a edição apenas nos arquivos de contrato (controller, DTOs, decorators Swagger) — nunca na lógica de negócio do use case/domínio.
|
|
55
|
-
|
|
56
|
-
## Não fazer
|
|
57
|
-
|
|
58
|
-
- Não expor entidade de domínio/persistência como corpo de resposta.
|
|
59
|
-
- Não reutilizar um DTO de criação para atualização parcial sem revisar `required`/`optional` de cada campo.
|
|
60
|
-
- Não introduzir uma segunda estratégia de versionamento no mesmo projeto.
|
|
61
|
-
- Não alterar regra de negócio do use case — este agente cuida apenas do contrato HTTP (ver skill `hexagonal` para a fronteira adapter/use case).
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: hexagonal
|
|
3
|
-
description: Diretrizes de arquitetura hexagonal (ports & adapters) aplicadas a projetos NestJS da DS9. Use ao criar, revisar ou refatorar módulos, definir camadas (domain/application/infrastructure), portas (interfaces) e adapters (controllers, repositórios, clients externos), ou quando o usuário pedir "arquitetura hexagonal", "ports and adapters" ou organização de um novo módulo/feature.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Arquitetura Hexagonal (Ports & Adapters) em NestJS
|
|
7
|
-
|
|
8
|
-
Padrão arquitetural oficial da DS9 para backends NestJS. Objetivo: isolar as regras de negócio (domínio) de frameworks, banco de dados e protocolos de entrada/saída, tornando o core testável e substituível.
|
|
9
|
-
|
|
10
|
-
## Camadas e responsabilidades
|
|
11
|
-
|
|
12
|
-
```text
|
|
13
|
-
src/
|
|
14
|
-
└── <contexto>/ # bounded context / módulo de negócio
|
|
15
|
-
├── domain/ # núcleo — sem dependência de NestJS, TypeORM, HTTP, etc.
|
|
16
|
-
│ ├── entities/ # entidades e value objects (regras e invariantes)
|
|
17
|
-
│ ├── ports/ # interfaces (contratos) que o domínio precisa
|
|
18
|
-
│ │ ├── in/ # casos de uso expostos (ex.: CreateOrderUseCase)
|
|
19
|
-
│ │ └── out/ # dependências externas (ex.: OrderRepositoryPort)
|
|
20
|
-
│ └── errors/ # exceções de domínio
|
|
21
|
-
├── application/ # orquestração dos casos de uso (services)
|
|
22
|
-
│ └── use-cases/ # implementa as portas "in", orquestra portas "out"
|
|
23
|
-
└── infrastructure/ # adapters concretos — tudo que é "detalhe"
|
|
24
|
-
├── adapters/
|
|
25
|
-
│ ├── in/ # entrada: controllers REST, consumers de fila, resolvers GraphQL
|
|
26
|
-
│ └── out/ # saída: repositórios TypeORM, HTTP clients, gateways de pagamento
|
|
27
|
-
└── <contexto>.module.ts # module NestJS que faz o "wiring" via injeção de dependência
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Regra de dependência (inversão obrigatória)
|
|
31
|
-
|
|
32
|
-
- `domain/` **não importa nada** de `infrastructure/`, `application/`, `@nestjs/*`, `typeorm`, `express`, etc. Só TypeScript puro.
|
|
33
|
-
- `application/` depende apenas de `domain/` (entidades e ports).
|
|
34
|
-
- `infrastructure/` depende de `domain/` e `application/`, nunca o contrário.
|
|
35
|
-
- Sentido das setas: `infrastructure` → `application` → `domain`. Nunca inverter.
|
|
36
|
-
|
|
37
|
-
## Ports (interfaces)
|
|
38
|
-
|
|
39
|
-
- Toda porta é uma `interface` TypeScript definida em `domain/ports/`.
|
|
40
|
-
- Ports de saída (`out`) representam o que o domínio **precisa** do mundo externo (persistência, mensageria, serviços externos). Nomeie como `<Algo>Port` (ex.: `OrderRepositoryPort`, `PaymentGatewayPort`).
|
|
41
|
-
- Ports de entrada (`in`) representam casos de uso que o domínio **oferece**. Nomeie como `<Ação><Entidade>UseCase` (ex.: `CreateOrderUseCase`).
|
|
42
|
-
- Use `Symbol`/token de injeção dedicado para cada porta (evita colisão e permite múltiplas implementações):
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
// domain/ports/out/order-repository.port.ts
|
|
46
|
-
export interface OrderRepositoryPort {
|
|
47
|
-
save(order: Order): Promise<void>;
|
|
48
|
-
findById(id: string): Promise<Order | null>;
|
|
49
|
-
}
|
|
50
|
-
export const ORDER_REPOSITORY_PORT = Symbol('ORDER_REPOSITORY_PORT');
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Adapters
|
|
54
|
-
|
|
55
|
-
- **Adapter de entrada (`in`)**: controller HTTP, consumer de fila, cron job, resolver GraphQL. Responsabilidade única: traduzir o protocolo externo (DTO/request) para a chamada do caso de uso, e o retorno do caso de uso para a resposta do protocolo. Nunca contém regra de negócio.
|
|
56
|
-
- **Adapter de saída (`out`)**: implementação concreta de uma port `out` (ex.: `TypeOrmOrderRepository implements OrderRepositoryPort`, usando as diretrizes da skill `typeorm`). Fica em `infrastructure/adapters/out/`.
|
|
57
|
-
- DTOs de entrada/saída (`class-validator`/`class-transformer`) vivem no adapter de entrada, nunca no domínio.
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// infrastructure/adapters/out/typeorm-order.repository.ts
|
|
61
|
-
@Injectable()
|
|
62
|
-
export class TypeOrmOrderRepository implements OrderRepositoryPort {
|
|
63
|
-
constructor(@InjectRepository(OrderEntity) private readonly repo: Repository<OrderEntity>) {}
|
|
64
|
-
|
|
65
|
-
async save(order: Order): Promise<void> {
|
|
66
|
-
await this.repo.save(OrderMapper.toPersistence(order));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async findById(id: string): Promise<Order | null> {
|
|
70
|
-
const row = await this.repo.findOneBy({ id });
|
|
71
|
-
return row ? OrderMapper.toDomain(row) : null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Wiring (module NestJS)
|
|
77
|
-
|
|
78
|
-
O module conecta a porta ao adapter concreto via `useClass`/token:
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
@Module({
|
|
82
|
-
controllers: [OrderController],
|
|
83
|
-
providers: [
|
|
84
|
-
CreateOrderUseCase,
|
|
85
|
-
{ provide: ORDER_REPOSITORY_PORT, useClass: TypeOrmOrderRepository },
|
|
86
|
-
],
|
|
87
|
-
})
|
|
88
|
-
export class OrderModule {}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Casos de uso recebem a porta por injeção, nunca a implementação concreta:
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
@Injectable()
|
|
95
|
-
export class CreateOrderUseCase {
|
|
96
|
-
constructor(@Inject(ORDER_REPOSITORY_PORT) private readonly orders: OrderRepositoryPort) {}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Transações cross-aggregate (Unit of Work)
|
|
101
|
-
|
|
102
|
-
Quando um caso de uso precisa alterar **mais de um agregado atrás de ports diferentes** na mesma operação atômica (ex.: criar pedido + baixar estoque), não force esse acoplamento dentro de um único repositório concreto. Use uma port dedicada de coordenação transacional:
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
// domain/ports/out/unit-of-work.port.ts
|
|
106
|
-
export interface UnitOfWorkPort {
|
|
107
|
-
run<T>(work: () => Promise<T>): Promise<T>;
|
|
108
|
-
}
|
|
109
|
-
export const UNIT_OF_WORK_PORT = Symbol('UNIT_OF_WORK_PORT');
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
- O use case injeta `UnitOfWorkPort` além das ports dos agregados envolvidos, e executa as chamadas das ports **dentro** do `run()`:
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
@Injectable()
|
|
116
|
-
export class CreateOrderUseCase {
|
|
117
|
-
constructor(
|
|
118
|
-
@Inject(UNIT_OF_WORK_PORT) private readonly uow: UnitOfWorkPort,
|
|
119
|
-
@Inject(ORDER_REPOSITORY_PORT) private readonly orders: OrderRepositoryPort,
|
|
120
|
-
@Inject(STOCK_REPOSITORY_PORT) private readonly stock: StockRepositoryPort,
|
|
121
|
-
) {}
|
|
122
|
-
|
|
123
|
-
async execute(cmd: CreateOrderCommand): Promise<Order> {
|
|
124
|
-
return this.uow.run(async () => {
|
|
125
|
-
const order = Order.create(cmd);
|
|
126
|
-
await this.stock.decrement(cmd.productId, cmd.quantity);
|
|
127
|
-
await this.orders.save(order);
|
|
128
|
-
return order;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
- O `UnitOfWorkPort` é a **única** peça com permissão de abrir uma transação técnica compartilhada; sua implementação concreta fica em `infrastructure/adapters/out/` (ver skill `typeorm` para a implementação com `DataSource.transaction()`).
|
|
135
|
-
- Não implemente coordenação cross-aggregate escrevendo em duas entidades de tabelas diferentes dentro do repositório de um único agregado (ex.: `OrderRepository` não deve tocar `StockEntity`) — isso quebra a regra de "repositório só implementa a port do seu próprio agregado".
|
|
136
|
-
|
|
137
|
-
## Mappers
|
|
138
|
-
|
|
139
|
-
- Toda tradução entre entidade de domínio e modelo de persistência (`OrderEntity` do TypeORM) passa por um `Mapper` dedicado (`toDomain` / `toPersistence`) em `infrastructure/adapters/out/mappers/`.
|
|
140
|
-
- O domínio nunca conhece decorators de ORM (`@Entity`, `@Column`).
|
|
141
|
-
|
|
142
|
-
## Checklist ao criar/revisar um módulo
|
|
143
|
-
|
|
144
|
-
1. O domínio compila sem `@nestjs/*` e sem `typeorm` nos imports?
|
|
145
|
-
2. Toda dependência externa do domínio está atrás de uma `port`?
|
|
146
|
-
3. Regras de negócio e invariantes estão nas entidades/use-cases, não nos controllers?
|
|
147
|
-
4. Existe um teste unitário do use-case usando um fake/mock da port (sem subir banco/HTTP)?
|
|
148
|
-
5. O adapter de entrada só faz tradução de protocolo (sem `if` de regra de negócio)?
|
|
149
|
-
6. Erros de domínio são classes próprias (`OrderNotFoundError`), traduzidas para HTTP só no adapter de entrada (`ExceptionFilter`)?
|
|
150
|
-
|
|
151
|
-
## Não fazer
|
|
152
|
-
|
|
153
|
-
- Não injete `Repository<Entity>` do TypeORM diretamente em use-cases ou entidades de domínio.
|
|
154
|
-
- Não coloque decorators de validação HTTP (`class-validator`) em entidades de domínio.
|
|
155
|
-
- Não exponha entidades de persistência (`*Entity`) fora de `infrastructure/`.
|
|
156
|
-
- Não crie "god services" que misturam orquestração de use-case com acesso direto a dados.
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: jest-testing
|
|
3
|
-
description: Diretrizes de testes com Jest para projetos NestJS/TypeScript da DS9 — testes unitários de use cases/services, testes de integração de controllers, mocks de ports, cobertura e convenções de nomenclatura. Use ao escrever/revisar testes, criar mocks/fakes, configurar `jest.config`, ou quando o usuário pedir "teste unitário", "teste de integração" ou "cobertura de testes".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Jest — Diretrizes de Teste DS9
|
|
7
|
-
|
|
8
|
-
Jest é o test runner padrão para projetos TypeScript/NestJS da DS9. Esta skill cobre convenções de teste; a organização em camadas segue a skill `hexagonal` e as convenções de framework seguem a skill `nestjs`.
|
|
9
|
-
|
|
10
|
-
## Estrutura e nomenclatura
|
|
11
|
-
|
|
12
|
-
- Arquivo de teste ao lado do arquivo testado: `order.use-case.ts` → `order.use-case.spec.ts` (unitário) ou `*.e2e-spec.ts` (ponta a ponta, pasta `test/`).
|
|
13
|
-
- `describe` nomeia a unidade testada (`describe('CreateOrderUseCase', ...)`); `it`/`test` descreve o comportamento esperado em linguagem natural (`it('deve lançar OrderNotFoundError quando o pedido não existe', ...)`), nunca o nome do método (`it('deve testar execute', ...)` é proibido).
|
|
14
|
-
- Um `describe` por método/comportamento público quando a classe tiver múltiplos cenários relevantes.
|
|
15
|
-
|
|
16
|
-
## Pirâmide de testes no projeto
|
|
17
|
-
|
|
18
|
-
| Camada | O que testar | Ferramenta | Sobe banco/HTTP real? |
|
|
19
|
-
|---|---|---|---|
|
|
20
|
-
| Domínio (entities, value objects) | Invariantes e regras de negócio puras | Jest puro | Não |
|
|
21
|
-
| Use cases / application | Orquestração, chamando ports mockadas | Jest + mocks das `ports` | Não |
|
|
22
|
-
| Adapters de saída (repositórios TypeORM) | Mapeamento correto entidade ↔ persistência | Jest + banco de teste (ou testcontainers, ver skill `docker`) | Sim (banco de teste isolado) |
|
|
23
|
-
| Controllers / adapters de entrada | Contrato HTTP: status, shape, validação | Jest + `supertest` (`*.e2e-spec.ts`) | Sim (app NestJS em memória, banco de teste) |
|
|
24
|
-
|
|
25
|
-
A maior parte dos testes deve estar nas duas primeiras camadas (rápidos, sem I/O); testes de integração/e2e cobrem os caminhos críticos, não todo caso de borda.
|
|
26
|
-
|
|
27
|
-
## Mocks de Ports (hexagonal)
|
|
28
|
-
|
|
29
|
-
- Use cases são testados mockando as `ports` (interfaces), nunca subindo banco/HTTP real. Prefira um *fake* em memória simples a um mock excessivamente configurado quando a port tem poucos métodos:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
class InMemoryOrderRepository implements OrderRepositoryPort {
|
|
33
|
-
private readonly rows = new Map<string, Order>();
|
|
34
|
-
async save(order: Order): Promise<void> { this.rows.set(order.id, order); }
|
|
35
|
-
async findById(id: string): Promise<Order | null> { return this.rows.get(id) ?? null; }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('CreateOrderUseCase', () => {
|
|
39
|
-
it('deve salvar o pedido com status PENDING', async () => {
|
|
40
|
-
const repo = new InMemoryOrderRepository();
|
|
41
|
-
const useCase = new CreateOrderUseCase(repo);
|
|
42
|
-
|
|
43
|
-
const order = await useCase.execute({ customerId: 'c1', items: [...] });
|
|
44
|
-
|
|
45
|
-
expect(order.status).toBe('PENDING');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
- Para mocks com `jest.fn()`, tipar sempre contra a interface da port (`jest.Mocked<OrderRepositoryPort>`), nunca `any`.
|
|
51
|
-
- Não mocke o que você não é dono (bibliotecas externas/SDKs) diretamente nos testes de domínio — isole-as atrás de uma port e mocke a port.
|
|
52
|
-
|
|
53
|
-
## Testes de controller (NestJS)
|
|
54
|
-
|
|
55
|
-
- Monte o módulo real via `Test.createTestingModule({ imports: [OrderModule] })`, sobrescrevendo apenas as dependências externas (`overrideProvider(ORDER_REPOSITORY_PORT)`), para validar o wiring de DI de verdade.
|
|
56
|
-
- Use `supertest` contra a instância HTTP da aplicação Nest (`app.getHttpServer()`), verificando status code, shape do body e erros de validação (400) com payload inválido.
|
|
57
|
-
|
|
58
|
-
## Assertions
|
|
59
|
-
|
|
60
|
-
- Prefira matchers específicos (`toEqual`, `toMatchObject`, `toThrow(OrderNotFoundError)`) a `toBeTruthy()`/`toBeDefined()` genéricos quando o valor exato importa.
|
|
61
|
-
- Para erros de domínio, sempre assertar a **classe/tipo** do erro (`expect(fn).rejects.toThrow(OrderNotFoundError)`), não só a mensagem em texto.
|
|
62
|
-
- Nunca deixe um teste sem nenhuma asserção (`it` vazio/`TODO`) marcado como passando; use `it.todo('...')` ou `.skip` explicitamente se o teste ainda não foi implementado.
|
|
63
|
-
|
|
64
|
-
## Isolamento e determinismo
|
|
65
|
-
|
|
66
|
-
- Cada teste deve poder rodar isoladamente e em qualquer ordem — sem estado global compartilhado entre `it`s (use `beforeEach` para recriar fakes/mocks).
|
|
67
|
-
- Nunca dependa de `Date.now()`/hora real sem controlar o clock (`jest.useFakeTimers()`); nunca dependa de ordem de `Promise`/timing sem `await` explícito.
|
|
68
|
-
- Testes de integração usam um banco de dados de teste próprio (schema isolado por execução), nunca o banco de dev/homolog compartilhado. Se o projeto usar testcontainers para subir esse banco, siga a skill `docker` para a imagem/configuração do container.
|
|
69
|
-
|
|
70
|
-
## Cobertura
|
|
71
|
-
|
|
72
|
-
- Cobertura mínima definida pelo projeto é validada no CI (`jest --coverage`); regras de negócio no domínio/use-cases devem ter cobertura próxima de 100% dos branches (if/else, early-return de erro).
|
|
73
|
-
- Cobertura alta não substitui qualidade das asserções — não escreva teste só para "bater número" sem validar comportamento real.
|
|
74
|
-
|
|
75
|
-
## Checklist ao escrever/revisar testes
|
|
76
|
-
|
|
77
|
-
1. O teste de use case mocka a port (sem banco/HTTP real)?
|
|
78
|
-
2. Nomes de `describe`/`it` descrevem comportamento, não implementação?
|
|
79
|
-
3. Erros de domínio são testados por tipo/classe, não só mensagem?
|
|
80
|
-
4. O teste é determinístico (sem dependência de tempo real/ordem)?
|
|
81
|
-
5. Testes de controller cobrem pelo menos um caso de validação inválida (400) além do caminho feliz?
|
|
82
|
-
|
|
83
|
-
## Não fazer
|
|
84
|
-
|
|
85
|
-
- Não usar `any` em mocks de ports.
|
|
86
|
-
- Não deixar `it.skip`/`xit` sem um comentário explicando o motivo e um acompanhamento (issue/TODO).
|
|
87
|
-
- Não testar detalhes de implementação privada (métodos privados) — teste pelo comportamento público.
|
|
88
|
-
- Não compartilhar estado mutável entre testes sem reset em `beforeEach`/`afterEach`.
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: nestjs
|
|
3
|
-
description: Diretrizes e convenções NestJS da DS9 — módulos, controllers, DTOs/validação, injeção de dependência, tratamento de erros, configuração e segurança. Use ao criar/revisar controllers, services, DTOs, guards, interceptors, pipes, ou quando o usuário pedir "endpoint", "controller", "module NestJS" ou "boas práticas NestJS".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# NestJS — Diretrizes DS9
|
|
7
|
-
|
|
8
|
-
Framework padrão de backend da DS9. Esta skill cobre convenções específicas do NestJS; a organização em camadas (domain/application/infrastructure) segue a skill `hexagonal`, e a persistência segue a skill `typeorm`.
|
|
9
|
-
|
|
10
|
-
## Estrutura de módulo
|
|
11
|
-
|
|
12
|
-
- Um módulo por bounded context/feature (`OrderModule`, `CustomerModule`), nunca um módulo "god" por tipo técnico (ex.: não criar `ControllersModule`).
|
|
13
|
-
- Cada módulo exporta só o que outros módulos precisam (`exports: [...]`); mantenha providers internos privados por padrão.
|
|
14
|
-
- `AppModule` apenas importa feature modules + módulos globais de infraestrutura (`ConfigModule`, `TypeOrmModule.forRoot`, etc.). Sem lógica de negócio.
|
|
15
|
-
|
|
16
|
-
## Controllers (adapters de entrada)
|
|
17
|
-
|
|
18
|
-
- Controller é "burro": recebe request, valida via DTO, chama um use case/service, mapeia o retorno. Nenhuma regra de negócio aqui (ver skill `hexagonal`).
|
|
19
|
-
- Um controller por recurso REST, rotas no plural (`/orders`, `/orders/:id`).
|
|
20
|
-
- Sempre tipar o retorno do handler e usar os decorators HTTP corretos (`@Get`, `@Post`, `@Patch` para update parcial, `@Put` para substituição completa, `@Delete`).
|
|
21
|
-
- Use `@HttpCode()` explícito quando o código padrão do método não for o desejado (ex.: `201` em `POST`, `204` em `DELETE`).
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
@Controller('orders')
|
|
25
|
-
export class OrderController {
|
|
26
|
-
constructor(private readonly createOrder: CreateOrderUseCase) {}
|
|
27
|
-
|
|
28
|
-
@Post()
|
|
29
|
-
@HttpCode(HttpStatus.CREATED)
|
|
30
|
-
async create(@Body() dto: CreateOrderDto): Promise<OrderResponseDto> {
|
|
31
|
-
const order = await this.createOrder.execute(dto.toCommand());
|
|
32
|
-
return OrderResponseDto.fromDomain(order);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## DTOs e validação
|
|
38
|
-
|
|
39
|
-
- Todo input externo (`@Body`, `@Query`, `@Param`) é tipado com uma classe DTO usando `class-validator` + `class-transformer`. Nunca aceitar `any`/objeto não tipado.
|
|
40
|
-
- Ative globalmente `ValidationPipe` com `whitelist: true` e `forbidNonWhitelisted: true` para rejeitar campos não declarados no DTO:
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
- DTOs de request e response são classes separadas (`CreateOrderDto` ≠ `OrderResponseDto`); nunca devolver a entidade de domínio/persistência diretamente na resposta HTTP.
|
|
47
|
-
- Documente cada DTO com decorators do Swagger (`@ApiProperty`) quando o projeto expõe OpenAPI.
|
|
48
|
-
|
|
49
|
-
## Injeção de dependência
|
|
50
|
-
|
|
51
|
-
- Prefira injeção por construtor (`constructor(private readonly x: X) {}`), nunca `new` direto de um provider gerenciado pelo Nest.
|
|
52
|
-
- Dependências para fora do domínio (ver skill `hexagonal`) são injetadas por token/interface (`@Inject(PORT_TOKEN)`), não pela implementação concreta.
|
|
53
|
-
- Escopo padrão dos providers é singleton (`Scope.DEFAULT`); use `Scope.REQUEST` apenas quando houver necessidade real de estado por request (tem custo de performance).
|
|
54
|
-
|
|
55
|
-
## Tratamento de erros
|
|
56
|
-
|
|
57
|
-
- Erros de domínio são classes próprias (`OrderNotFoundError extends Error`), lançadas pelo use case/domínio.
|
|
58
|
-
- Um `ExceptionFilter` global traduz erros de domínio para `HttpException`/status code correto; controllers não fazem `try/catch` manual para mapear erro de negócio em HTTP.
|
|
59
|
-
- Nunca deixe vazar stack trace ou detalhes internos (query SQL, caminho de arquivo) na resposta HTTP em produção.
|
|
60
|
-
- Erros de validação (DTO) já retornam 400 automaticamente via `ValidationPipe`; não duplique essa validação no controller.
|
|
61
|
-
|
|
62
|
-
## Configuração
|
|
63
|
-
|
|
64
|
-
- Toda configuração de ambiente passa por `@nestjs/config` com schema de validação (ex.: `Joi`/`zod`) no bootstrap — a aplicação **falha ao subir** se faltar uma env var obrigatória, nunca falha silenciosamente em runtime.
|
|
65
|
-
- Nunca leia `process.env` diretamente em services/controllers; sempre via `ConfigService` tipado.
|
|
66
|
-
- Segredos (DB password, JWT secret, API keys) vêm de variável de ambiente/secret manager, nunca hardcoded ou commitados.
|
|
67
|
-
|
|
68
|
-
## Guards, Interceptors e Pipes
|
|
69
|
-
|
|
70
|
-
- Autenticação/autorização: `Guard` (`CanActivate`), nunca checagem manual de token dentro do controller.
|
|
71
|
-
- Transformação de resposta cross-cutting (ex.: envelope padrão, log de tempo de resposta): `Interceptor`.
|
|
72
|
-
- Validação/transformação de parâmetros individuais: `Pipe` (além do `ValidationPipe` global).
|
|
73
|
-
- Guards/interceptors de autorização específicos de negócio (ex.: "só o dono do pedido pode ver") ficam dentro do módulo de feature correspondente (ex.: `OrderModule`), na camada `infrastructure/adapters/in/` (ver skill `hexagonal`) — nunca em `domain/` (um `Guard`/`Interceptor` sempre importa `@nestjs/common`, o que viola a regra de pureza do domínio) nem em um guard genérico compartilhado por todo o app.
|
|
74
|
-
|
|
75
|
-
## Logging e observabilidade
|
|
76
|
-
|
|
77
|
-
- Use o `Logger` do Nest (ou logger estruturado configurado no projeto), nunca `console.log` em código de produção.
|
|
78
|
-
- Logs de erro incluem contexto (ex.: `orderId`, `userId`) mas nunca dados sensíveis (senha, token, PII completo).
|
|
79
|
-
- Toda rota crítica deve ter log de entrada/saída ou métrica associada (latência, taxa de erro) conforme padrão de observabilidade do projeto.
|
|
80
|
-
|
|
81
|
-
## Testes
|
|
82
|
-
|
|
83
|
-
- Services/use cases: teste unitário com `Test.createTestingModule` mockando as dependências (ports). Ver skill `jest-testing`.
|
|
84
|
-
- Controllers: teste de integração (`supertest`) cobrindo status code, shape da resposta e validação de DTO.
|
|
85
|
-
- Nunca suba banco real em teste unitário; use fakes/mocks das ports (ver skill `hexagonal`).
|
|
86
|
-
|
|
87
|
-
## Checklist ao criar/revisar um endpoint
|
|
88
|
-
|
|
89
|
-
1. Controller só traduz protocolo (sem regra de negócio)?
|
|
90
|
-
2. Request e response têm DTOs próprios e validados?
|
|
91
|
-
3. Erros de domínio são mapeados via `ExceptionFilter`, não `try/catch` ad hoc?
|
|
92
|
-
4. Configuração/segredos vêm de `ConfigService`, não de `process.env` direto?
|
|
93
|
-
5. Existe teste cobrindo o caminho feliz e pelo menos um erro de validação/negócio?
|
|
94
|
-
|
|
95
|
-
## Não fazer
|
|
96
|
-
|
|
97
|
-
- Não injetar `Repository`/clients externos diretamente em controllers.
|
|
98
|
-
- Não retornar entidades de domínio/persistência cruas na resposta HTTP.
|
|
99
|
-
- Não usar `any` em parâmetros de rota/body.
|
|
100
|
-
- Não usar `console.log` como mecanismo de log em produção.
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: typeorm
|
|
3
|
-
description: Diretrizes de uso do TypeORM como adapter de persistência em projetos NestJS/hexagonal da DS9 — entities, repositórios, migrations, relations, transações e mappers. Use ao criar/alterar entities, repositórios, migrations do TypeORM, ou quando o usuário pedir "entity", "migration TypeORM", "repository" ou "relação" (OneToMany, ManyToMany etc.).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# TypeORM — Diretrizes DS9
|
|
7
|
-
|
|
8
|
-
TypeORM é o ORM padrão para a camada de persistência (`infrastructure/adapters/out/`) nos projetos NestJS/hexagonal da DS9, sobre banco MySQL (ver skill `mysql`). O domínio nunca conhece TypeORM diretamente (ver skill `hexagonal`).
|
|
9
|
-
|
|
10
|
-
## Localização no projeto
|
|
11
|
-
|
|
12
|
-
- Entities (`@Entity`), repositórios concretos e migrations ficam em `infrastructure/`, nunca em `domain/` ou `application/`.
|
|
13
|
-
- Entidade de persistência (`OrderEntity`) é uma classe **separada** da entidade de domínio (`Order`). A tradução entre as duas é feita por um `Mapper` dedicado (`OrderMapper.toDomain` / `toPersistence`), conforme a skill `hexagonal`.
|
|
14
|
-
|
|
15
|
-
## Configuração
|
|
16
|
-
|
|
17
|
-
- Sempre usar `migrationsRun: false` em produção — migrations rodam explicitamente no pipeline de deploy, nunca automaticamente no boot da aplicação.
|
|
18
|
-
- `synchronize: false` **sempre**, em todos os ambientes (inclusive dev/teste). Schema é controlado só por migrations, nunca por auto-sync do TypeORM (risco de perda de dados em produção).
|
|
19
|
-
- Configuração de conexão vem de `ConfigService` (ver skill `nestjs`), nunca hardcoded.
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
TypeOrmModule.forRootAsync({
|
|
23
|
-
useFactory: (config: ConfigService) => ({
|
|
24
|
-
type: 'mysql',
|
|
25
|
-
host: config.getOrThrow('DB_HOST'),
|
|
26
|
-
port: config.getOrThrow('DB_PORT'),
|
|
27
|
-
synchronize: false,
|
|
28
|
-
migrationsRun: false,
|
|
29
|
-
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
|
30
|
-
migrations: [__dirname + '/migrations/*{.ts,.js}'],
|
|
31
|
-
}),
|
|
32
|
-
inject: [ConfigService],
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Entities
|
|
37
|
-
|
|
38
|
-
- Nome da classe: `<Nome>Entity` (ex.: `OrderEntity`), tabela em `snake_case` plural via `@Entity('orders')` (ver convenções da skill `mysql`).
|
|
39
|
-
- Toda coluna usa `@Column` com tipo explícito (`type: 'decimal', precision: 19, scale: 4` para dinheiro — nunca deixar o TypeORM inferir tipo monetário).
|
|
40
|
-
- Datas: `@CreateDateColumn()` / `@UpdateDateColumn()` para `created_at`/`updated_at`; `@DeleteDateColumn()` se o projeto usa soft delete (`deleted_at`).
|
|
41
|
-
- Chave primária: `@PrimaryGeneratedColumn()` (auto-increment) ou `@PrimaryGeneratedColumn('uuid')`/`@PrimaryColumn()` conforme a estratégia de ID definida no projeto — manter consistência em todo o schema.
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
@Entity('orders')
|
|
45
|
-
export class OrderEntity {
|
|
46
|
-
@PrimaryGeneratedColumn('uuid')
|
|
47
|
-
id: string;
|
|
48
|
-
|
|
49
|
-
@Column({ type: 'decimal', precision: 19, scale: 4 })
|
|
50
|
-
totalAmount: string; // decimal do MySQL vem como string — converter explicitamente no mapper
|
|
51
|
-
|
|
52
|
-
@ManyToOne(() => CustomerEntity, { nullable: false })
|
|
53
|
-
@JoinColumn({ name: 'customer_id' })
|
|
54
|
-
customer: CustomerEntity;
|
|
55
|
-
|
|
56
|
-
@CreateDateColumn({ name: 'created_at' })
|
|
57
|
-
createdAt: Date;
|
|
58
|
-
|
|
59
|
-
@UpdateDateColumn({ name: 'updated_at' })
|
|
60
|
-
updatedAt: Date;
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Relations
|
|
65
|
-
|
|
66
|
-
- Declare `@JoinColumn({ name: '...' })` explicitamente em toda relação `@ManyToOne`/`@OneToOne` — nunca deixe o TypeORM inferir o nome da coluna FK.
|
|
67
|
-
- Evite `eager: true` em relations de entidades grandes/listas (causa N+1 e joins não intencionais); prefira carregar explicitamente via `relations: [...]` ou `QueryBuilder` quando necessário.
|
|
68
|
-
- `@ManyToMany` só quando a tabela de junção não tem atributos próprios; se a relação carrega dados extras (ex.: `quantity` em `order_items`), modele como entidade própria com duas `@ManyToOne`.
|
|
69
|
-
- Nunca use `cascade: true` sem necessidade explícita e documentada — cascatas implícitas de delete/update são fonte comum de perda de dados acidental.
|
|
70
|
-
|
|
71
|
-
## Repositórios (adapters de saída)
|
|
72
|
-
|
|
73
|
-
- Repositório concreto implementa a `port` definida no domínio (`OrderRepositoryPort`), conforme a skill `hexagonal`; nunca exponha `Repository<OrderEntity>` do TypeORM fora do adapter.
|
|
74
|
-
- Prefira `Repository`/`QueryBuilder` injetado via `@InjectRepository(OrderEntity)` a `EntityManager` cru, exceto em casos que exigem múltiplas entidades na mesma transação.
|
|
75
|
-
- Use `QueryBuilder` (não SQL raw) para queries complexas — mantém parametrização automática (proteção contra SQL Injection) e portabilidade.
|
|
76
|
-
- Pagine toda listagem (`skip`/`take` ou keyset) e nunca retorne `find()` sem filtro em tabelas grandes.
|
|
77
|
-
|
|
78
|
-
## Migrations
|
|
79
|
-
|
|
80
|
-
- Gere migrations via CLI (`typeorm migration:generate`) a partir da comparação de entities, e **revise manualmente** o SQL gerado antes de commitar (o gerador pode incluir drops/alters não intencionais).
|
|
81
|
-
- Toda migration tem `up()` e `down()` funcionais — ver regras detalhadas de migration na skill `mysql`.
|
|
82
|
-
- Nomeie migrations com timestamp + descrição clara (`1700000000000-CreateOrdersTable.ts`, `1700000100000-AddStatusToOrders.ts`).
|
|
83
|
-
- Nunca edite uma migration já aplicada em qualquer ambiente compartilhado (dev remoto, homolog, produção); crie uma nova migration corretiva.
|
|
84
|
-
|
|
85
|
-
## Transações
|
|
86
|
-
|
|
87
|
-
- Operações que tocam uma única port/agregado usam a transação implícita de cada chamada (`save`, `update`); para operações **cross-aggregate** (múltiplas ports), implemente a `UnitOfWorkPort` definida na skill `hexagonal` usando `DataSource.transaction()`:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// infrastructure/adapters/out/typeorm-unit-of-work.ts
|
|
91
|
-
@Injectable()
|
|
92
|
-
export class TypeOrmUnitOfWork implements UnitOfWorkPort {
|
|
93
|
-
constructor(private readonly dataSource: DataSource) {}
|
|
94
|
-
|
|
95
|
-
async run<T>(work: () => Promise<T>): Promise<T> {
|
|
96
|
-
return this.dataSource.transaction(async () => work());
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
- Dentro de `work()`, os repositórios concretos (`TypeOrmOrderRepository`, `TypeOrmStockRepository`) devem operar sobre o `QueryRunner`/`EntityManager` **da transação ativa**, não sobre o `Repository` injetado globalmente — normalmente resolvido com `AsyncLocalStorage` ou passando o `EntityManager` corrente via contexto de request, conforme o padrão de UoW adotado pelo projeto.
|
|
102
|
-
- Nunca abra uma transação e faça chamadas de rede (HTTP/fila) dentro dela — mantenha transações curtas (ver skill `mysql`).
|
|
103
|
-
- Não implemente a coordenação cross-aggregate manipulando entidades de outro agregado dentro do repositório de um agregado diferente (ex.: `TypeOrmOrderRepository` não deve escrever em `StockEntity`); isso é responsabilidade exclusiva do `UnitOfWorkPort` + use case (ver skill `hexagonal`).
|
|
104
|
-
|
|
105
|
-
## Checklist ao criar/revisar entity/migration/repositório
|
|
106
|
-
|
|
107
|
-
1. Entity e migration estão em `infrastructure/`, nunca vazando para `domain/application`?
|
|
108
|
-
2. Tipos de coluna corretos (`decimal` para dinheiro, FKs com `@JoinColumn` explícito)?
|
|
109
|
-
3. `synchronize: false` e migration revisada manualmente?
|
|
110
|
-
4. Repositório implementa a port do domínio e usa `QueryBuilder`/`Repository` parametrizado (sem SQL concatenado)?
|
|
111
|
-
5. Operações multi-entidade usam transação explícita?
|
|
112
|
-
6. Existe mapper `toDomain`/`toPersistence` separando entidade de persistência da entidade de domínio?
|
|
113
|
-
|
|
114
|
-
## Não fazer
|
|
115
|
-
|
|
116
|
-
- Não usar `synchronize: true` em nenhum ambiente.
|
|
117
|
-
- Não expor `Entity`/`Repository` do TypeORM fora da camada de infraestrutura.
|
|
118
|
-
- Não usar `cascade: true` ou `eager: true` sem justificativa documentada.
|
|
119
|
-
- Não escrever SQL raw concatenando valores de usuário.
|