@ds9-solutions/mcp 0.1.0 → 0.3.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/LICENSE +43 -0
- package/README.md +53 -0
- 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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Caminho: test/routes/api/orders/index_test.dart
|
|
2
|
+
// Rota testada como função, com RequestContext mockado; sempre cobre o 405.
|
|
3
|
+
import 'dart:io';
|
|
4
|
+
|
|
5
|
+
import 'package:dart_frog/dart_frog.dart';
|
|
6
|
+
import 'package:mocktail/mocktail.dart';
|
|
7
|
+
import 'package:test/test.dart';
|
|
8
|
+
|
|
9
|
+
import '../../../../routes/api/orders/index.dart' as route;
|
|
10
|
+
import 'package:my_api/common/middleware/auth_middleware.dart';
|
|
11
|
+
import 'package:my_api/orders/adapter/controllers/order_controller.dart';
|
|
12
|
+
|
|
13
|
+
class _MockRequestContext extends Mock implements RequestContext {}
|
|
14
|
+
|
|
15
|
+
class _MockOrderController extends Mock implements OrderController {}
|
|
16
|
+
|
|
17
|
+
void main() {
|
|
18
|
+
late _MockRequestContext context;
|
|
19
|
+
|
|
20
|
+
setUp(() {
|
|
21
|
+
context = _MockRequestContext();
|
|
22
|
+
when(() => context.read<OrderController>()).thenReturn(_MockOrderController());
|
|
23
|
+
when(() => context.read<AuthenticatedUser>()).thenReturn(const AuthenticatedUser(id: 'u1'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('retorna 405 para método não suportado', () async {
|
|
27
|
+
when(() => context.request).thenReturn(Request.delete(Uri.parse('http://localhost/api/orders')));
|
|
28
|
+
|
|
29
|
+
final response = await route.onRequest(context);
|
|
30
|
+
|
|
31
|
+
expect(response.statusCode, equals(HttpStatus.methodNotAllowed));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Caminho: lib/orders/domain/services/order_service.dart
|
|
2
|
+
import 'dart:io';
|
|
3
|
+
|
|
4
|
+
import 'package:fpdart/fpdart.dart';
|
|
5
|
+
|
|
6
|
+
import '../../../common/exception/exceptions.dart';
|
|
7
|
+
import '../models/order.dart';
|
|
8
|
+
import '../ports/order_repository_port.dart';
|
|
9
|
+
|
|
10
|
+
/// Regras de negócio de pedidos.
|
|
11
|
+
class OrderService {
|
|
12
|
+
/// Cria o serviço com o [_repository] injetado pelo middleware.
|
|
13
|
+
const OrderService(this._repository);
|
|
14
|
+
|
|
15
|
+
final OrderRepositoryPort _repository;
|
|
16
|
+
|
|
17
|
+
/// Busca o pedido [id] e garante que pertence a [requesterId].
|
|
18
|
+
TaskEither<BusinessException, Order> getOwnedById({
|
|
19
|
+
required String id,
|
|
20
|
+
required String requesterId,
|
|
21
|
+
}) {
|
|
22
|
+
return _repository.findById(id).flatMap((order) {
|
|
23
|
+
if (order == null) return TaskEither.left(_failure(HttpStatus.notFound, 'Pedido não encontrado.'));
|
|
24
|
+
if (order.ownerId != requesterId) {
|
|
25
|
+
return TaskEither.left(_failure(HttpStatus.forbidden, 'Acesso negado.'));
|
|
26
|
+
}
|
|
27
|
+
return TaskEither.right(order);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Cria um pedido para [ownerId]; rejeita [total] não positivo.
|
|
32
|
+
TaskEither<BusinessException, Order> create({
|
|
33
|
+
required String ownerId,
|
|
34
|
+
required double total,
|
|
35
|
+
}) {
|
|
36
|
+
if (total <= 0) {
|
|
37
|
+
return TaskEither.left(_failure(HttpStatus.badRequest, 'O total deve ser positivo.'));
|
|
38
|
+
}
|
|
39
|
+
return _repository.create(ownerId: ownerId, total: total);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
BusinessException _failure(int statusCode, String message) {
|
|
43
|
+
return BusinessException(
|
|
44
|
+
domainName: 'order',
|
|
45
|
+
statusCode: statusCode,
|
|
46
|
+
message: message,
|
|
47
|
+
error: message,
|
|
48
|
+
stackTrace: StackTrace.current,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Caminho: test/orders/domain/services/order_service_test.dart
|
|
2
|
+
// Service testado com fake do port, sem banco real.
|
|
3
|
+
import 'dart:io';
|
|
4
|
+
|
|
5
|
+
import 'package:fpdart/fpdart.dart';
|
|
6
|
+
import 'package:test/test.dart';
|
|
7
|
+
|
|
8
|
+
import 'package:my_api/common/exception/exceptions.dart';
|
|
9
|
+
import 'package:my_api/orders/domain/models/order.dart';
|
|
10
|
+
import 'package:my_api/orders/domain/ports/order_repository_port.dart';
|
|
11
|
+
import 'package:my_api/orders/domain/services/order_service.dart';
|
|
12
|
+
|
|
13
|
+
class _FakeRepository implements OrderRepositoryPort {
|
|
14
|
+
_FakeRepository(this.stored);
|
|
15
|
+
|
|
16
|
+
final Order? stored;
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
TaskEither<BusinessException, Order?> findById(String id) => TaskEither.right(stored);
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
TaskEither<BusinessException, Order> create({
|
|
23
|
+
required String ownerId,
|
|
24
|
+
required double total,
|
|
25
|
+
}) =>
|
|
26
|
+
TaskEither.right(Order(id: '1', ownerId: ownerId, total: total));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void main() {
|
|
30
|
+
group('OrderService.getOwnedById', () {
|
|
31
|
+
test('retorna Left 404 quando o pedido não existe', () async {
|
|
32
|
+
final service = OrderService(_FakeRepository(null));
|
|
33
|
+
|
|
34
|
+
final result = await service.getOwnedById(id: '1', requesterId: 'u1').run();
|
|
35
|
+
|
|
36
|
+
expect(result.getLeft().toNullable()?.statusCode, HttpStatus.notFound);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('retorna Left 403 quando o pedido é de outro usuário', () async {
|
|
40
|
+
final service = OrderService(_FakeRepository(const Order(id: '1', ownerId: 'u2', total: 5)));
|
|
41
|
+
|
|
42
|
+
final result = await service.getOwnedById(id: '1', requesterId: 'u1').run();
|
|
43
|
+
|
|
44
|
+
expect(result.getLeft().toNullable()?.statusCode, HttpStatus.forbidden);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('retorna Right quando o pedido pertence ao usuário', () async {
|
|
48
|
+
final service = OrderService(_FakeRepository(const Order(id: '1', ownerId: 'u1', total: 5)));
|
|
49
|
+
|
|
50
|
+
final result = await service.getOwnedById(id: '1', requesterId: 'u1').run();
|
|
51
|
+
|
|
52
|
+
expect(result.isRight(), isTrue);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Dart Frog — Checklist de review detalhado
|
|
2
|
+
|
|
3
|
+
## Pré-requisitos
|
|
4
|
+
- [ ] `dart_frog` e `fpdart` presentes no `pubspec.yaml` (senão, o usuário foi informado e autorizou a instalação).
|
|
5
|
+
- [ ] Estrutura existente preservada; padrão aplicado ao código novo.
|
|
6
|
+
|
|
7
|
+
## Inegociáveis
|
|
8
|
+
- [ ] Contratos de port/service/repository/controller retornam `TaskEither`.
|
|
9
|
+
- [ ] Operações que podem lançar usam `TaskEither.tryCatch`.
|
|
10
|
+
- [ ] Toda rota termina o `switch` com `case _` → 405.
|
|
11
|
+
- [ ] Dependências compostas só em `_middleware.dart` (`provider`/`context.read`).
|
|
12
|
+
- [ ] `BusinessException`, `ClientException`, `MiddlewareException` nas camadas certas.
|
|
13
|
+
- [ ] Domínio agnóstico de persistência (só ports).
|
|
14
|
+
|
|
15
|
+
## Camadas
|
|
16
|
+
- [ ] Rota fina; só a rota executa `.run()`.
|
|
17
|
+
- [ ] Controller não acessa persistência nem tem regra de negócio.
|
|
18
|
+
- [ ] Service sem HTTP, `RequestContext` ou SDK.
|
|
19
|
+
- [ ] Repository sem regra de negócio; falhas convertidas em `BusinessException`.
|
|
20
|
+
- [ ] Autorização de recurso no service, não no middleware.
|
|
21
|
+
|
|
22
|
+
## Erros e segurança
|
|
23
|
+
- [ ] Sem stack trace, SQL, `error` original ou mensagem de SDK na resposta.
|
|
24
|
+
- [ ] Formato `{error:{code,message}}` único.
|
|
25
|
+
- [ ] Segredos no ambiente; nada sensível em logs.
|
|
26
|
+
- [ ] Papel/perfil do cliente nunca confiado sem validação no servidor.
|
|
27
|
+
|
|
28
|
+
## Qualidade
|
|
29
|
+
- [ ] Skill `dart` seguida (`///` nas declarações públicas, nomenclatura, OOP).
|
|
30
|
+
- [ ] Arquivos gerados regenerados e não editados à mão.
|
|
31
|
+
- [ ] Testes: service, controller, rota (com 405).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Dart Frog — Erros (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 5 do `SKILL.md`. Exemplos: `examples/exceptions.dart`, `error_response.dart`.
|
|
4
|
+
|
|
5
|
+
## Camadas fixas
|
|
6
|
+
|
|
7
|
+
| Exceção | Quem lança (`Left`) | Campos |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `BusinessException` | service, repository | `domainName`, `statusCode`, `message`, `error`, `stackTrace` |
|
|
10
|
+
| `ClientException` | controller | `statusCode`, `message` |
|
|
11
|
+
| `MiddlewareException` | middleware | `statusCode`, `message`, `stackTrace?` |
|
|
12
|
+
|
|
13
|
+
- `message` é **sempre** segura para o cliente. `error` e `stackTrace` são só para log.
|
|
14
|
+
- Ao converter entre camadas, preserve o `statusCode`. Para `5xx`, a mensagem ao cliente é genérica.
|
|
15
|
+
- Não use `Exception` genérica como falha de `TaskEither`.
|
|
16
|
+
|
|
17
|
+
## Status HTTP
|
|
18
|
+
|
|
19
|
+
| Situação | Status |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Entrada inválida, body malformado | 400 |
|
|
22
|
+
| Não autenticado ou credencial inválida | 401 |
|
|
23
|
+
| Autenticado sem permissão (ex.: recurso de outro usuário) | 403 |
|
|
24
|
+
| Recurso inexistente | 404 |
|
|
25
|
+
| Método não suportado | 405 |
|
|
26
|
+
| Conflito de estado | 409 |
|
|
27
|
+
| Falha interna ou de persistência | 500 |
|
|
28
|
+
| Criação com sucesso | 201 |
|
|
29
|
+
|
|
30
|
+
## Formato único
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{ "error": { "code": "not_found", "message": "Pedido não encontrado." } }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Nunca
|
|
37
|
+
|
|
38
|
+
- Enviar stack trace, SQL, causa original (`error`), caminhos, credenciais ou mensagens de SDK.
|
|
39
|
+
- Devolver `f.error.toString()` ao cliente.
|
|
40
|
+
- Capturar erro e responder sucesso vazio.
|
|
41
|
+
- Logar tokens, documentos, e-mail, telefone ou conteúdo sensível completo.
|
|
42
|
+
|
|
43
|
+
## Onde cada falha nasce
|
|
44
|
+
|
|
45
|
+
- Formato de body/query/params: controller/DTO (`400`).
|
|
46
|
+
- Regra de negócio e autorização de recurso: service (`403`/`404`/`409`).
|
|
47
|
+
- Persistência ou integração: repository (`500`, com `error` original preservado).
|
|
48
|
+
- Autenticação e rate limit: middleware (`401`/`429`).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Dart Frog — `fpdart` e `TaskEither` (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 4 do `SKILL.md`. Exemplos: `examples/repository.dart`, `service.dart`, `controller.dart`.
|
|
4
|
+
|
|
5
|
+
## Pré-requisito: `fpdart` instalado
|
|
6
|
+
|
|
7
|
+
1. Procure `fpdart` em `dependencies` no `pubspec.yaml`.
|
|
8
|
+
2. Se não estiver, **informe que ele é obrigatório para seguir o padrão DS9** e peça autorização para instalar: `dart pub add fpdart`.
|
|
9
|
+
3. Não gere código fora do padrão para contornar a ausência.
|
|
10
|
+
|
|
11
|
+
## Regras
|
|
12
|
+
|
|
13
|
+
- Todo contrato de camada que pode falhar retorna `TaskEither<Falha, T>`: `BusinessException` (port/service/repository), `ClientException` (controller).
|
|
14
|
+
- Operação assíncrona que pode lançar vai dentro de `TaskEither.tryCatch(run, onError)`; `onError` converte a causa (`error`, `stackTrace`) na exceção da camada, sem perder a causa original.
|
|
15
|
+
- Composição com `map`, `flatMap` e `mapLeft`. Regra violada: `TaskEither.left(...)`; sucesso: `TaskEither.right(...)`.
|
|
16
|
+
- **Só a rota executa** (`.run()`), e usa `match` para produzir a `Response`.
|
|
17
|
+
- Funções puras e sem I/O (validações locais, construtores, `toJson`) podem retornar valores comuns, ou `Either` quando houver falha de validação.
|
|
18
|
+
- Proibido: `throw` que escape da camada, `catch (_)` amplo, `Exception` genérica como falha, falha convertida em sucesso vazio.
|
|
19
|
+
|
|
20
|
+
## Padrões
|
|
21
|
+
|
|
22
|
+
```dart
|
|
23
|
+
// 1) Acesso externo (repository)
|
|
24
|
+
TaskEither<BusinessException, Order?> findById(String id) => TaskEither.tryCatch(
|
|
25
|
+
() => _dataSource.read(id),
|
|
26
|
+
(error, stackTrace) => BusinessException(
|
|
27
|
+
domainName: 'order',
|
|
28
|
+
statusCode: HttpStatus.internalServerError,
|
|
29
|
+
message: 'Falha ao consultar o pedido.',
|
|
30
|
+
error: error,
|
|
31
|
+
stackTrace: stackTrace,
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// 2) Regra de negócio (service)
|
|
36
|
+
_repository.findById(id).flatMap(
|
|
37
|
+
(order) => order == null ? TaskEither.left(_notFound(id)) : TaskEither.right(order),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// 3) Troca de camada de erro (controller)
|
|
41
|
+
_service.getOwnedById(id: id, requesterId: userId).mapLeft(_toClient).map(OrderResponse.fromDomain);
|
|
42
|
+
|
|
43
|
+
// 4) Execução (rota)
|
|
44
|
+
final result = await controller.getById(id, user.id).run();
|
|
45
|
+
return result.match(ErrorResponse.fromClient, (order) => Response.json(body: order.toJson()));
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Cuidados
|
|
49
|
+
|
|
50
|
+
- Mantenha as cadeias legíveis: se passar de 3 ou 4 etapas, extraia métodos privados nomeados.
|
|
51
|
+
- Não misture `Future` simples e `TaskEither` no mesmo contrato.
|
|
52
|
+
- Teste `Left` e `Right` explicitamente (`result.isLeft()`, `result.getLeft()`, `result.getRight()`).
|
|
53
|
+
- Esta é a exceção funcional aceita pela skill `dart`: vale só para os contratos entre camadas. O restante continua orientado a objetos.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Dart Frog — Responsabilidade por camada (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 3 do `SKILL.md`. Exemplos: `examples/`.
|
|
4
|
+
|
|
5
|
+
## Route (`routes/**`)
|
|
6
|
+
|
|
7
|
+
Faz: `switch` no método HTTP, `context.read<Controller>()`, `.run()` do `TaskEither`, converte `Left`/`Right` em `Response`, `case _` com 405.
|
|
8
|
+
Não faz: regra de negócio, query, SDK, instanciar dependência, ler body (isso é do controller).
|
|
9
|
+
|
|
10
|
+
## Controller (`adapter/controllers`)
|
|
11
|
+
|
|
12
|
+
Faz: lê body/query/path/providers do `RequestContext` recebido **por chamada**, converte para DTO e valida formato, chama o service, converte modelo → DTO, converte `BusinessException` em `ClientException`.
|
|
13
|
+
Retorna `TaskEither<ClientException, T>`. Não acessa banco, não tem regra de negócio, não monta `Response`.
|
|
14
|
+
O controller é um objeto **provido por middleware** com o service no construtor; o `RequestContext` entra como parâmetro dos métodos.
|
|
15
|
+
|
|
16
|
+
## Service (`domain/services`)
|
|
17
|
+
|
|
18
|
+
Faz: regras de negócio, autorização de domínio (ex.: o pedido pertence ao usuário), composição de ports com `flatMap`/`map`.
|
|
19
|
+
Retorna `TaskEither<BusinessException, T>`. Recebe ports no construtor (`final`), não conhece HTTP, headers, `RequestContext`, SDK ou persistência, não instancia dependências.
|
|
20
|
+
|
|
21
|
+
## Port (`domain/ports`)
|
|
22
|
+
|
|
23
|
+
`abstract interface class` pequeno e focado. Assinaturas só com modelos de domínio e tipos simples (nunca DTO, `RequestContext`, `Response`, tipos de ORM ou SDK). Retorna `TaskEither<BusinessException, T>`.
|
|
24
|
+
|
|
25
|
+
## Repository (`adapter/repository`)
|
|
26
|
+
|
|
27
|
+
Implementa o port, acessa a persistência (via adapter/fonte de dados), mapeia linha → modelo, envolve cada acesso em `TaskEither.tryCatch` convertendo a causa em `BusinessException`. Sem regra de negócio ou de autorização. A tecnologia concreta é decidida pela skill de persistência do projeto.
|
|
28
|
+
|
|
29
|
+
## DTO (`adapter/dtos`)
|
|
30
|
+
|
|
31
|
+
Entrada: `fromJson` + `validate()` (formato). Saída: `fromDomain` + `toJson()`. Nome indica a operação (`CreateOrderRequest`, `OrderResponse`).
|
|
32
|
+
|
|
33
|
+
## Model (`domain/models`)
|
|
34
|
+
|
|
35
|
+
Classes imutáveis (`final`, `const`), sem conhecer DTO ou persistência. Invariantes validadas na criação quando possível.
|
|
36
|
+
|
|
37
|
+
## Middleware
|
|
38
|
+
|
|
39
|
+
Autentica, compõe providers, encerra com `MiddlewareException`. Não substitui a cadeia controller → service → repository nem contém regra de negócio.
|
|
40
|
+
|
|
41
|
+
## Comunicação entre contextos
|
|
42
|
+
|
|
43
|
+
Um contexto consome outro via port ou service público, nunca via repository concreto; sem dependência circular.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Dart Frog — Middlewares e injeção de dependência (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 7 do `SKILL.md`. Exemplos: `examples/middleware_composition.dart`, `auth_middleware.dart`.
|
|
4
|
+
|
|
5
|
+
## Regra
|
|
6
|
+
|
|
7
|
+
A composição de dependências acontece **somente** em `routes/**/_middleware.dart`, com `provider<T>`; a leitura é `context.read<T>()`. Nunca instancie service, repository ou controller no handler, nem use service locator.
|
|
8
|
+
|
|
9
|
+
## Um `_middleware.dart` por diretório
|
|
10
|
+
|
|
11
|
+
Exporta `Handler middleware(Handler handler)` e vale para a pasta e subpastas. Encadeie com `handler.use(...)`.
|
|
12
|
+
|
|
13
|
+
## Ordem de resolução
|
|
14
|
+
|
|
15
|
+
A resolução é de baixo para cima: se `B` depende de `A`, declare `.use(B).use(A)`. Exemplo: controller → service → repository.
|
|
16
|
+
|
|
17
|
+
## Cache
|
|
18
|
+
|
|
19
|
+
`provider` é lazy e recria o valor a cada leitura. Para instâncias compartilhadas ou caras, faça cache em variável privada do arquivo: `_service ??= OrderService(...)`.
|
|
20
|
+
|
|
21
|
+
## Distribuição por nível
|
|
22
|
+
|
|
23
|
+
| Middleware | Provê |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `routes/_middleware.dart` | CORS, infraestrutura compartilhada (conexão, configuração, rate limit) |
|
|
26
|
+
| `routes/api/_middleware.dart` | autenticação, usuário autenticado |
|
|
27
|
+
| `routes/admin/_middleware.dart` | autenticação + verificação de papel administrativo |
|
|
28
|
+
| `routes/<grupo>/<recurso>/_middleware.dart` | controller/service/repository do recurso |
|
|
29
|
+
|
|
30
|
+
Cada middleware específico provê só o necessário ao seu ramo.
|
|
31
|
+
|
|
32
|
+
## Autenticação
|
|
33
|
+
|
|
34
|
+
Middleware valida credenciais por um **port** (ex.: `TokenVerifierPort`), provê o usuário e encerra com `MiddlewareException` (`401`/`403`) quando inválido. Credenciais e chaves vêm do ambiente, nunca do código.
|
|
35
|
+
|
|
36
|
+
## Proibido
|
|
37
|
+
|
|
38
|
+
- Regra de negócio em middleware (inclusive verificar dono do recurso): isso é do service.
|
|
39
|
+
- Ler parâmetro de caminho por posição (`pathSegments[n]`) em middleware: frágil e acoplado à URL. Receba o `id` na rota e valide no service.
|
|
40
|
+
- `context.read` em models, ports, services e repositories.
|
|
41
|
+
- Segundo ponto de composição fora de `_middleware.dart`.
|
|
42
|
+
|
|
43
|
+
## Variáveis de ambiente
|
|
44
|
+
|
|
45
|
+
Leia `Platform.environment` em um ponto único (`common/environment`) e proveja a configuração. `String.fromEnvironment` não funciona no Dart Frog.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Dart Frog — Estrutura de projeto (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 2 do `SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Árvore completa
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
<projeto>/
|
|
9
|
+
├── routes/
|
|
10
|
+
│ ├── _middleware.dart # raiz: CORS, infraestrutura compartilhada, rate limit
|
|
11
|
+
│ ├── index.dart # GET /
|
|
12
|
+
│ ├── public/ # sem autenticação
|
|
13
|
+
│ │ └── _middleware.dart
|
|
14
|
+
│ ├── api/ # usuários autenticados
|
|
15
|
+
│ │ ├── _middleware.dart # autenticação (provê o usuário)
|
|
16
|
+
│ │ └── orders/
|
|
17
|
+
│ │ ├── _middleware.dart # composição do contexto
|
|
18
|
+
│ │ ├── index.dart # /api/orders
|
|
19
|
+
│ │ └── [id].dart # /api/orders/:id
|
|
20
|
+
│ └── admin/ # operações administrativas
|
|
21
|
+
│ └── _middleware.dart # autenticação + papel administrativo
|
|
22
|
+
├── lib/
|
|
23
|
+
│ ├── orders/ # um contexto de negócio
|
|
24
|
+
│ │ ├── domain/
|
|
25
|
+
│ │ │ ├── models/ # entidades e valores
|
|
26
|
+
│ │ │ ├── ports/ # contratos (abstract interface class)
|
|
27
|
+
│ │ │ └── services/ # regras de negócio
|
|
28
|
+
│ │ └── adapter/
|
|
29
|
+
│ │ ├── controllers/ # HTTP -> domínio
|
|
30
|
+
│ │ ├── dtos/ # entrada e saída da API
|
|
31
|
+
│ │ └── repository/ # domínio -> persistência
|
|
32
|
+
│ └── common/
|
|
33
|
+
│ ├── exception/ # BusinessException, ClientException, MiddlewareException
|
|
34
|
+
│ ├── http/ # formato de erro, helpers de resposta
|
|
35
|
+
│ ├── middleware/ # auth, CORS, rate limit reutilizáveis
|
|
36
|
+
│ ├── environment/ # leitura única de variáveis de ambiente
|
|
37
|
+
│ ├── logger/
|
|
38
|
+
│ └── models/ # paginação e tipos compartilhados
|
|
39
|
+
├── test/ # espelha lib/ e routes/
|
|
40
|
+
├── main.dart # só init() e run()
|
|
41
|
+
├── pubspec.yaml
|
|
42
|
+
└── analysis_options.yaml
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Variações permitidas
|
|
46
|
+
|
|
47
|
+
- `application/use_case/` dentro de um contexto, **só** quando houver orquestração entre vários services/ports. Não crie por antecipação.
|
|
48
|
+
- `adapter/<integração>/` (e-mail, mensageria, storage) para SDKs externos, sempre atrás de um port.
|
|
49
|
+
- Infraestrutura de persistência compartilhada em `common/infra/` (detalhes na skill de persistência do projeto).
|
|
50
|
+
- Não crie pasta `mappers/` por antecipação: o mapeamento fica no repository (linha → modelo) e no controller/DTO (modelo → DTO).
|
|
51
|
+
|
|
52
|
+
## Nomenclatura de arquivos
|
|
53
|
+
|
|
54
|
+
`snake_case.dart` com sufixo do papel: `_model`, `_dto`, `_service`, `_service_port`, `_repository`, `_repository_port`, `_controller`, `_middleware`, `_use_case`. Demais regras: skill `dart`.
|
|
55
|
+
|
|
56
|
+
## Grupos de rota
|
|
57
|
+
|
|
58
|
+
| Grupo | Pasta | Middleware |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| Base | `routes/index.dart` | raiz |
|
|
61
|
+
| Pública | `routes/public/**` | rate limit, sem autenticação |
|
|
62
|
+
| Autenticada | `routes/api/**` | autenticação, provê o usuário |
|
|
63
|
+
| Administrativa | `routes/admin/**` | autenticação + papel administrativo |
|
|
64
|
+
|
|
65
|
+
## Imports
|
|
66
|
+
|
|
67
|
+
Dentro de `lib/`, use imports relativos (skill `dart`). Em `routes/`, `main.dart` e `test/` use `package:<nome_do_pacote>/...`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Dart Frog — Rotas (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 6 do `SKILL.md`. Exemplos: `examples/route_collection.dart`, `route_item.dart`.
|
|
4
|
+
|
|
5
|
+
## Convenções do roteador
|
|
6
|
+
|
|
7
|
+
- Uma rota = um arquivo em `routes/` que exporta `onRequest(RequestContext context, ...)`. `index.dart` mapeia para a pasta.
|
|
8
|
+
- Rota dinâmica: `[id].dart` ou pasta `[id]/`; cada parâmetro vira argumento `String` posicional, na ordem do caminho.
|
|
9
|
+
- Wildcard `[...rest].dart` deve ser o único arquivo da pasta.
|
|
10
|
+
- Não coexistam `routes/api.dart` e `routes/api/index.dart` (conflito no build).
|
|
11
|
+
- Handler assíncrono retorna `Future<Response>`.
|
|
12
|
+
|
|
13
|
+
## 405 obrigatório
|
|
14
|
+
|
|
15
|
+
Todo handler despacha por método e termina com o caso padrão:
|
|
16
|
+
|
|
17
|
+
```dart
|
|
18
|
+
switch (context.request.method) {
|
|
19
|
+
case HttpMethod.get:
|
|
20
|
+
// ...
|
|
21
|
+
case _:
|
|
22
|
+
return Response(statusCode: HttpStatus.methodNotAllowed);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Rotas inexistentes (caminho não encontrado) são tratadas pelo próprio Dart Frog com 404; o 405 cobre método não suportado em uma rota existente.
|
|
27
|
+
|
|
28
|
+
## Request e Response
|
|
29
|
+
|
|
30
|
+
- O body só pode ser lido uma vez (`body()`, `json()` ou `formData()`); a leitura é do controller.
|
|
31
|
+
- `json()` devolve `dynamic`: valide e converta para DTO tipado; payload inválido responde 400.
|
|
32
|
+
- `formData()` lança `StateError` se o content-type não for de formulário: verifique antes.
|
|
33
|
+
- Sucesso: `Response.json(body: ...)`; criação: `201`; use `HttpStatus` em vez de números.
|
|
34
|
+
- Listagens mantêm o formato adotado pelo endpoint (lista direta ou objeto com dados e metadados).
|
|
35
|
+
|
|
36
|
+
## Paginação
|
|
37
|
+
|
|
38
|
+
Parâmetros `page` e `pageSize` na query, lidos no controller; o repository devolve itens, total, página e tamanho; o service só propaga. Defina um tipo compartilhado em `common/models` e mantenha o padrão em todos os endpoints.
|
|
39
|
+
|
|
40
|
+
## CLI
|
|
41
|
+
|
|
42
|
+
- `dart pub global activate dart_frog_cli` instala a CLI.
|
|
43
|
+
- `dart_frog create <nome>`, `dart_frog dev` (hot reload; `--port`), `dart_frog build` (build de produção em `build/`, com Dockerfile).
|
|
44
|
+
- `dart_frog new route "/caminho"` e `dart_frog new middleware "/caminho"`: prefira a CLI a criar à mão.
|
|
45
|
+
- Versões da CLI e do pacote `dart_frog` devem ser compatíveis.
|
|
46
|
+
|
|
47
|
+
## Estáticos
|
|
48
|
+
|
|
49
|
+
Arquivos estáticos ficam em `public/` na raiz; não repita nomes existentes em `routes/`.
|
|
50
|
+
|
|
51
|
+
## Ciclo de vida
|
|
52
|
+
|
|
53
|
+
`main.dart` define só `init()` (execução única no start) e `run()` (entrypoint customizado; roda a cada hot reload). Não compõe services.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Dart Frog — Testes (referência)
|
|
2
|
+
|
|
3
|
+
Complementa a seção 9 do `SKILL.md`. Exemplos: `examples/service_test.dart`, `route_test.dart`. Para criar e executar, use o agente `test-writer`.
|
|
4
|
+
|
|
5
|
+
## Estrutura
|
|
6
|
+
|
|
7
|
+
`test/` espelha `lib/` e `routes/` (ex.: `test/orders/domain/services/order_service_test.dart`, `test/routes/api/orders/index_test.dart`). Ferramentas: `package:test` e `package:mocktail`.
|
|
8
|
+
|
|
9
|
+
## O que testar
|
|
10
|
+
|
|
11
|
+
| Alvo | Como | Cobrir |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Service | fake/mock do port, sem banco real | cada regra de negócio, `Left` com status correto e `Right` |
|
|
14
|
+
| Controller | service mockado e `RequestContext` mockado | entrada válida, body inválido, validação, `Left`→`ClientException`, `Right` |
|
|
15
|
+
| Repository | integração separada, quando houver persistência real | mapeamento e conversão de falha em `BusinessException` |
|
|
16
|
+
| Rota | `RequestContext` mockado, handler como função | cada método suportado **e o 405** |
|
|
17
|
+
| Middleware | `Handler` interno stubado | provê o valor; encerra com 401/403 |
|
|
18
|
+
|
|
19
|
+
## Dicas
|
|
20
|
+
|
|
21
|
+
- Mocke o `RequestContext` e faça stub de `context.request` (com `Request.get/post/delete` reais) e de `context.read<T>()`.
|
|
22
|
+
- Verifique `Either` com `result.isLeft()`, `result.getLeft()`, `result.getRight()`.
|
|
23
|
+
- Sem serviços externos reais em testes unitários.
|
|
24
|
+
- Toda regra de negócio nova tem teste no service.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docker
|
|
3
|
-
description: Diretrizes de containerização Docker para projetos
|
|
3
|
+
description: Diretrizes de containerização Docker para projetos Node.js da DS9 — Dockerfile multi-stage, docker-compose para ambiente local, segurança de imagem e variáveis de ambiente. Use ao criar/revisar `Dockerfile`, `docker-compose.yml`, ou quando o usuário pedir "containerizar", "imagem Docker" ou "subir ambiente local".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Docker — Diretrizes DS9
|
|
7
7
|
|
|
8
|
-
Convenções para containerizar aplicações
|
|
8
|
+
Convenções para containerizar aplicações Node.js e seus serviços de apoio (MySQL, etc.) nos projetos da empresa.
|
|
9
9
|
|
|
10
10
|
## Dockerfile multi-stage (obrigatório)
|
|
11
11
|
|
|
@@ -42,7 +42,7 @@ Regras obrigatórias:
|
|
|
42
42
|
|
|
43
43
|
## Variáveis de ambiente e segredos
|
|
44
44
|
|
|
45
|
-
- Nenhum segredo (senha de banco, JWT secret, API key) é copiado para dentro da imagem ou escrito em `ENV` no Dockerfile. Segredos entram em runtime via variáveis de ambiente/secret manager do orquestrador (
|
|
45
|
+
- Nenhum segredo (senha de banco, JWT secret, API key) é copiado para dentro da imagem ou escrito em `ENV` no Dockerfile. Segredos entram em runtime via variáveis de ambiente/secret manager do orquestrador (use o mecanismo de configuração do projeto).
|
|
46
46
|
- `.env` usado em desenvolvimento local nunca é commitado (deve estar no `.gitignore`) nem copiado para a imagem (`.dockerignore`).
|
|
47
47
|
- `docker-compose.yml` de desenvolvimento pode referenciar um `.env` local via `env_file`, mas o arquivo de exemplo versionado é `.env.example` sem valores reais.
|
|
48
48
|
|