@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
|
@@ -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
|
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: driftorm
|
|
3
|
+
description: Convenções e boas práticas do Drift, a biblioteca de banco de dados type-safe para Dart e Flutter (SQLite e Postgres). Use ao criar, editar, revisar ou testar tabelas, consultas, DAOs, migrations ou a classe de banco em projetos que dependem de drift.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Drift best practices
|
|
7
|
+
|
|
8
|
+
Baseado na documentação oficial: https://drift.simonbinder.eu (repositório simolus3/drift, `drift` 2.35.x).
|
|
9
|
+
|
|
10
|
+
## Princípio central
|
|
11
|
+
|
|
12
|
+
O Drift gera código a partir do schema que você declara. O schema é a fonte da verdade: tabelas em Dart (ou SQL em arquivos `.drift`), uma classe de banco central e o `build_runner` gerando o resto. Toda mudança de schema é uma migration versionada e testada.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
| Alvo | Dependências | Dev dependencies |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Flutter | `drift`, `drift_flutter`, `path_provider` | `drift_dev`, `build_runner` |
|
|
19
|
+
| Dart (SQLite) | `drift`, `sqlite3` | `drift_dev`, `build_runner` |
|
|
20
|
+
| Dart (Postgres) | `drift`, `postgres`, `drift_postgres` | `drift_dev`, `build_runner` |
|
|
21
|
+
|
|
22
|
+
- `dart run build_runner build` gera o código uma vez. Durante o desenvolvimento, use `dart run build_runner watch`.
|
|
23
|
+
- Para Postgres, configure `sql: dialects: [postgres]` no `build.yaml`. Por padrão o gerador só emite SQL de SQLite.
|
|
24
|
+
|
|
25
|
+
## Regras
|
|
26
|
+
|
|
27
|
+
### Classe de banco
|
|
28
|
+
|
|
29
|
+
1. **Uma classe `@DriftDatabase(tables: [...], daos: [...])`** que estende `_$NomeDatabase`, com `part 'arquivo.g.dart';`.
|
|
30
|
+
2. **O construtor aceita um `QueryExecutor` opcional**: `AppDatabase([QueryExecutor? e]) : super(e ?? _openConnection());`. Isso permite injetar um banco em memória nos testes.
|
|
31
|
+
3. **Use uma única instância (singleton) por arquivo de banco.** Abrir a mesma classe várias vezes sobre o mesmo arquivo quebra os streams e gera aviso em runtime. Forneça a instância por DI (Riverpod, get_it, provider).
|
|
32
|
+
4. **Abra a conexão fora da thread principal.** No Flutter, use `driftDatabase(name: ...)`, do `drift_flutter`. Em Dart puro, use `NativeDatabase.createInBackground(File(...))`. Ambos rodam o SQLite em isolate.
|
|
33
|
+
5. **Feche com `await db.close()`** no fim do ciclo de vida (testes, shutdown do servidor).
|
|
34
|
+
|
|
35
|
+
### Tabelas
|
|
36
|
+
|
|
37
|
+
6. **Uma tabela é uma classe que estende `Table`**, com colunas declaradas como `late final campo = tipo()();`. Os parênteses finais são obrigatórios.
|
|
38
|
+
7. **Colunas são não-nulas por padrão.** Use `.nullable()` só quando `null` tiver significado de domínio.
|
|
39
|
+
8. **Chave primária:** `integer().autoIncrement()()` para o caso comum. Para outros casos, sobrescreva `Set<Column> get primaryKey => {...}` (só com `=>` e literal de set).
|
|
40
|
+
9. **Prefira `clientDefault()` a `withDefault()`.** O `clientDefault` é calculado em Dart e não exige migration. O `withDefault` altera o schema.
|
|
41
|
+
10. **Chaves estrangeiras com `.references(Tabela, #coluna, onDelete: ...)`.** No SQLite elas vêm desligadas, então ative com `PRAGMA foreign_keys = ON` no `beforeOpen`.
|
|
42
|
+
11. **Garanta integridade no schema:** `.unique()`, `uniqueKeys` para unicidade composta, `.check(expr)` para validação. Prefira `check` a `withLength`, que só valida em Dart.
|
|
43
|
+
12. **Crie índices com `@TableIndex(name: ..., columns: {#col})`** para colunas muito filtradas. Chave primária, `unique` e alvo de FK já têm índice.
|
|
44
|
+
13. **Em projeto novo, armazene `DateTime` como texto ISO-8601** com `store_date_time_values_as_text: true` no `build.yaml`. O padrão é timestamp Unix em segundos, sem fuso e sem frações.
|
|
45
|
+
14. **Reaproveite colunas comuns** (`id`, `createdAt`) com mixins de tabela.
|
|
46
|
+
15. **Enums e tipos próprios** usam `intEnum<T>()`, `textEnum<T>()` ou `TypeConverter`, em vez de conversão manual espalhada pelo código.
|
|
47
|
+
16. **Rode o gerador de novo** sempre que mudar tabelas, DAOs ou o annotation do banco.
|
|
48
|
+
|
|
49
|
+
### Consultas e escrita
|
|
50
|
+
|
|
51
|
+
17. **Insira com `Companion.insert(...)`**, que torna obrigatórios só os campos sem default. Para atualização parcial, use `Companion(campo: Value(x))`.
|
|
52
|
+
18. **Use a API tipada** (`select`, `into`, `update`, `delete`, joins, manager) antes de SQL cru. Quando precisar de SQL, prefira arquivos `.drift`, que o gerador valida em tempo de build. `customSelect` fica como último recurso e precisa de `readsFrom` para funcionar com `watch`.
|
|
53
|
+
19. **`get()` para leitura única e `watch()` para UI reativa.** O stream já emite o valor atual ao ser escutado, então não combine `get()` com `watch()`.
|
|
54
|
+
20. **`getSingle()` lança erro se não houver exatamente uma linha.** Quando a ausência é um caso válido, use `getSingleOrNull()` ou `watchSingleOrNull()`.
|
|
55
|
+
|
|
56
|
+
### Organização
|
|
57
|
+
|
|
58
|
+
21. **Separe as consultas por domínio em DAOs:** `@DriftAccessor(tables: [...])` estendendo `DatabaseAccessor<AppDatabase>` com o mixin gerado. Registre os DAOs em `daos:` no `@DriftDatabase`.
|
|
59
|
+
22. **Não exponha a classe de banco para a UI ou para os handlers.** Exponha DAOs ou repositórios que retornam modelos e streams.
|
|
60
|
+
|
|
61
|
+
### Transações
|
|
62
|
+
|
|
63
|
+
23. **Operações que precisam ser atômicas vão em `transaction(() async { ... })`.**
|
|
64
|
+
24. **Faça `await` em todas as chamadas dentro da transação.** Query sem `await` pode rodar depois do fechamento e causar perda de dados ou crash. Não agende timers que usem o banco dentro da transação.
|
|
65
|
+
25. **Streams criados fora da transação só são notificados no commit.** Não escute fora da transação um stream criado dentro dela.
|
|
66
|
+
|
|
67
|
+
### Migrations
|
|
68
|
+
|
|
69
|
+
26. **Toda mudança de schema exige incrementar o `schemaVersion` e escrever uma migration.** O Drift não altera bancos existentes sozinho.
|
|
70
|
+
27. **Use o fluxo guiado `make-migrations`.** Registre o banco em `databases:` no `build.yaml` e rode `dart run drift_dev make-migrations` **antes** da primeira mudança, para salvar o schema inicial. Depois, a cada mudança, incremente a versão e rode o comando de novo.
|
|
71
|
+
28. **Escreva as migrations com `stepByStep(from1To2: ..., from2To3: ...)`**, usando o `schema` recebido e nunca as tabelas atuais da classe. Extraia o `stepByStep` para um getter de extensão em `GeneratedDatabase`, para não referenciar o schema atual por acidente.
|
|
72
|
+
29. **Rode os testes de migration gerados** (em `test/drift/`) e versione a pasta `drift_schemas/` no Git.
|
|
73
|
+
30. **Evite consultas dentro de callbacks de migration.** Se precisar, rode-as só depois de o schema chegar ao estado que elas esperam.
|
|
74
|
+
31. **Pragmas e seed vão no `beforeOpen`**, que roda a cada abertura. Use `details.wasCreated` e `details.hadUpgrade` para condicionar o seed.
|
|
75
|
+
|
|
76
|
+
### Testes
|
|
77
|
+
|
|
78
|
+
32. **Teste com `NativeDatabase.memory()`**, abrindo no `setUp` e fechando no `tearDown`. Em widget tests do Flutter, envolva com `DatabaseConnection(..., closeStreamsSynchronously: true)`.
|
|
79
|
+
|
|
80
|
+
## Exemplos
|
|
81
|
+
|
|
82
|
+
### Tabelas, banco e conexão (Flutter)
|
|
83
|
+
|
|
84
|
+
```dart
|
|
85
|
+
// lib/database/database.dart
|
|
86
|
+
import 'package:drift/drift.dart';
|
|
87
|
+
import 'package:drift_flutter/drift_flutter.dart';
|
|
88
|
+
import 'package:path_provider/path_provider.dart';
|
|
89
|
+
|
|
90
|
+
import 'database.steps.dart';
|
|
91
|
+
|
|
92
|
+
part 'database.g.dart';
|
|
93
|
+
|
|
94
|
+
mixin AutoIncrementingPrimaryKey on Table {
|
|
95
|
+
late final id = integer().autoIncrement()();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class Categories extends Table with AutoIncrementingPrimaryKey {
|
|
99
|
+
late final name = text().unique()();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@TableIndex(name: 'todo_category', columns: {#category})
|
|
103
|
+
class TodoItems extends Table with AutoIncrementingPrimaryKey {
|
|
104
|
+
// Coluna que se referencia no check precisa de tipo explícito.
|
|
105
|
+
late final Column<String> title =
|
|
106
|
+
text().check(title.length.isBiggerThanValue(0))();
|
|
107
|
+
late final done = boolean().clientDefault(() => false)();
|
|
108
|
+
late final category = integer()
|
|
109
|
+
.nullable()
|
|
110
|
+
.references(Categories, #id, onDelete: KeyAction.setNull)();
|
|
111
|
+
late final createdAt = dateTime().clientDefault(DateTime.now)();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@DriftDatabase(tables: [Categories, TodoItems], daos: [TodosDao])
|
|
115
|
+
class AppDatabase extends _$AppDatabase {
|
|
116
|
+
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
|
117
|
+
|
|
118
|
+
@override
|
|
119
|
+
int get schemaVersion => 2;
|
|
120
|
+
|
|
121
|
+
@override
|
|
122
|
+
MigrationStrategy get migration => MigrationStrategy(
|
|
123
|
+
onUpgrade: _schemaUpgrade,
|
|
124
|
+
beforeOpen: (details) async {
|
|
125
|
+
await customStatement('PRAGMA foreign_keys = ON');
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
static QueryExecutor _openConnection() {
|
|
130
|
+
return driftDatabase(
|
|
131
|
+
name: 'app_database',
|
|
132
|
+
native: const DriftNativeOptions(
|
|
133
|
+
databaseDirectory: getApplicationSupportDirectory,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
extension Migrations on GeneratedDatabase {
|
|
140
|
+
OnUpgrade get _schemaUpgrade => stepByStep(
|
|
141
|
+
from1To2: (m, schema) async {
|
|
142
|
+
await m.createTable(schema.categories);
|
|
143
|
+
await m.addColumn(schema.todoItems, schema.todoItems.category);
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### DAO com stream e transação
|
|
150
|
+
|
|
151
|
+
```dart
|
|
152
|
+
// lib/database/todos_dao.dart
|
|
153
|
+
import 'package:drift/drift.dart';
|
|
154
|
+
|
|
155
|
+
import 'database.dart';
|
|
156
|
+
|
|
157
|
+
part 'todos_dao.g.dart';
|
|
158
|
+
|
|
159
|
+
@DriftAccessor(tables: [TodoItems, Categories])
|
|
160
|
+
class TodosDao extends DatabaseAccessor<AppDatabase> with _$TodosDaoMixin {
|
|
161
|
+
TodosDao(super.attachedDatabase);
|
|
162
|
+
|
|
163
|
+
Stream<List<TodoItem>> watchPending() {
|
|
164
|
+
return (select(todoItems)
|
|
165
|
+
..where((t) => t.done.equals(false))
|
|
166
|
+
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
|
|
167
|
+
.watch();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Future<int> add(String title) {
|
|
171
|
+
return into(todoItems).insert(TodoItemsCompanion.insert(title: title));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Future<void> deleteCategory(int categoryId) {
|
|
175
|
+
return transaction(() async {
|
|
176
|
+
await (update(todoItems)..where((t) => t.category.equals(categoryId)))
|
|
177
|
+
.write(const TodoItemsCompanion(category: Value(null)));
|
|
178
|
+
await (delete(categories)..where((c) => c.id.equals(categoryId))).go();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Conexão Postgres (backend Dart)
|
|
185
|
+
|
|
186
|
+
```dart
|
|
187
|
+
import 'dart:io';
|
|
188
|
+
|
|
189
|
+
import 'package:drift_postgres/drift_postgres.dart';
|
|
190
|
+
import 'package:postgres/postgres.dart' as pg;
|
|
191
|
+
|
|
192
|
+
QueryExecutor openPostgres() {
|
|
193
|
+
final env = Platform.environment;
|
|
194
|
+
return PgDatabase(
|
|
195
|
+
endpoint: pg.Endpoint(
|
|
196
|
+
host: env['DB_HOST']!,
|
|
197
|
+
database: env['DB_NAME']!,
|
|
198
|
+
username: env['DB_USER'],
|
|
199
|
+
password: env['DB_PASSWORD'],
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Teste
|
|
206
|
+
|
|
207
|
+
```dart
|
|
208
|
+
import 'package:drift/drift.dart';
|
|
209
|
+
import 'package:drift/native.dart';
|
|
210
|
+
import 'package:test/test.dart';
|
|
211
|
+
|
|
212
|
+
import 'package:my_app/database/database.dart';
|
|
213
|
+
|
|
214
|
+
void main() {
|
|
215
|
+
late AppDatabase db;
|
|
216
|
+
|
|
217
|
+
setUp(() {
|
|
218
|
+
db = AppDatabase(
|
|
219
|
+
DatabaseConnection(
|
|
220
|
+
NativeDatabase.memory(),
|
|
221
|
+
closeStreamsSynchronously: true,
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
tearDown(() => db.close());
|
|
227
|
+
|
|
228
|
+
test('watchPending emite o item recém-criado', () async {
|
|
229
|
+
final stream = db.todosDao.watchPending();
|
|
230
|
+
await db.todosDao.add('Escrever testes');
|
|
231
|
+
|
|
232
|
+
final items = await stream.first;
|
|
233
|
+
expect(items.map((t) => t.title), contains('Escrever testes'));
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Checklist de revisão
|
|
239
|
+
|
|
240
|
+
- [ ] Mudou tabela, DAO ou annotation? O `build_runner` foi rodado de novo?
|
|
241
|
+
- [ ] Mudou o schema? O `schemaVersion` foi incrementado, o `make-migrations` foi rodado e existe um passo `fromXToY` testado?
|
|
242
|
+
- [ ] A migration usa `schema.*` e não as tabelas atuais da classe?
|
|
243
|
+
- [ ] Existe uma única instância do banco, injetada por DI?
|
|
244
|
+
- [ ] A conexão usa `driftDatabase` ou `createInBackground`?
|
|
245
|
+
- [ ] FKs no SQLite têm `PRAGMA foreign_keys = ON`?
|
|
246
|
+
- [ ] Toda chamada dentro de `transaction` tem `await`?
|
|
247
|
+
- [ ] Defaults usam `clientDefault`, salvo quando o default precisa estar no schema?
|
|
248
|
+
- [ ] `getSingle` é usado só onde a linha certamente existe?
|
|
249
|
+
- [ ] Os testes usam `NativeDatabase.memory()` e fecham o banco no `tearDown`?
|