@ds9-solutions/mcp 0.3.0 → 0.5.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/README.md +1 -1
- package/content/agents/code-reviewer.agent.md +20 -0
- package/content/agents/db-analyser.agent.md +18 -2
- package/content/agents/security-reviewer.agent.md +21 -1
- package/content/agents/test-writer.agent.md +17 -8
- package/content/skills/dart/SKILL.md +19 -0
- package/content/skills/dart-frog/SKILL.md +22 -1
- package/content/skills/debugger/SKILL.md +144 -0
- package/content/skills/debugger/references/red-flags.md +32 -0
- package/content/skills/debugger/references/report-format.md +65 -0
- package/content/skills/debugger/references/root-cause-tracing.md +42 -0
- package/content/skills/debugger/references/safe-diagnostics.md +47 -0
- package/content/skills/driftorm/SKILL.md +59 -225
- package/content/skills/driftorm/examples/connection_postgres.dart +25 -0
- package/content/skills/driftorm/examples/connection_sqlite.dart +13 -0
- package/content/skills/driftorm/examples/database.dart +25 -0
- package/content/skills/driftorm/examples/migration_manual.dart +24 -0
- package/content/skills/driftorm/examples/migration_step_by_step.dart +24 -0
- package/content/skills/driftorm/examples/pagination.dart +48 -0
- package/content/skills/driftorm/examples/queries.dart +34 -0
- package/content/skills/driftorm/examples/repository_hex.dart +63 -0
- package/content/skills/driftorm/examples/table.dart +25 -0
- package/content/skills/driftorm/examples/test.dart +33 -0
- package/content/skills/driftorm/postgres/connection.md +9 -0
- package/content/skills/driftorm/postgres/migrations.md +11 -0
- package/content/skills/driftorm/postgres/types-and-defaults.md +10 -0
- package/content/skills/driftorm/references/anti-patterns.md +20 -0
- package/content/skills/driftorm/references/checklist.md +17 -0
- package/content/skills/driftorm/references/database-and-connection.md +22 -0
- package/content/skills/driftorm/references/dialects.md +17 -0
- package/content/skills/driftorm/references/drift-files.md +20 -0
- package/content/skills/driftorm/references/migrations.md +26 -0
- package/content/skills/driftorm/references/performance.md +10 -0
- package/content/skills/driftorm/references/queries-and-pagination.md +46 -0
- package/content/skills/driftorm/references/repositories-taskeither.md +29 -0
- package/content/skills/driftorm/references/setup.md +37 -0
- package/content/skills/driftorm/references/tables-and-columns.md +43 -0
- package/content/skills/driftorm/references/testing.md +12 -0
- package/content/skills/driftorm/references/transactions.md +23 -0
- package/content/skills/driftorm/sqlite/connection.md +11 -0
- package/content/skills/driftorm/sqlite/types-and-pragmas.md +9 -0
- package/content/skills/flutter/SKILL.md +19 -0
- package/content/skills/hex-pattern-dart/SKILL.md +21 -0
- package/content/skills/hex-pattern-dart/examples/adapter-in.md +4 -5
- package/content/skills/hex-pattern-dart/references/persistence.md +2 -2
- package/package.json +1 -1
- package/content/skills/docker/SKILL.md +0 -104
- package/content/skills/mysql/SKILL.md +0 -94
- package/content/skills/typescript/SKILL.md +0 -75
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Postgres — migrations
|
|
2
|
+
|
|
3
|
+
- Projeto novo: fluxo guiado (`make-migrations` + `stepByStep`) em `references/migrations.md`.
|
|
4
|
+
- **Projeto existente com migrations manuais** (`if (from < n)` + `customStatement`): mantenha o estilo, aplique as regras abaixo.
|
|
5
|
+
- DDL idempotente: `CREATE TABLE IF NOT EXISTS`, `ADD COLUMN IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`.
|
|
6
|
+
- Postgres suporta DDL transacional: agrupe a migration em uma transação para que falha parcial faça rollback.
|
|
7
|
+
- Adicionar coluna `NOT NULL` em tabela com dados: adicione nullable (ou com default), preencha, depois imponha `NOT NULL`.
|
|
8
|
+
- Índices grandes em produção: `CREATE INDEX CONCURRENTLY` não roda em transação; trate fora da migration do app.
|
|
9
|
+
- Nunca interpole entrada externa em DDL; use só literais fixos.
|
|
10
|
+
- Mantenha `schemaVersion` incrementado a cada bloco; `beforeOpen` não deve alterar schema.
|
|
11
|
+
- Em produção, prefira rodar migrations em um passo controlado (deploy/CI) a rodar em cada instância no startup, quando houver várias réplicas.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Postgres — tipos e defaults
|
|
2
|
+
|
|
3
|
+
- **UUID:** `text().withDefault(const CustomExpression('gen_random_uuid()'))()` é legítimo (default no banco; vale para inserts fora do app). Alternativa: `clientDefault` gerando uuid em Dart. Mantenha o que o projeto usa.
|
|
4
|
+
- **Timestamps:** `dateTime().withDefault(currentDateAndTime)()` ou `CustomExpression('now()')`. Prefira `timestamptz` quando o tipo fizer parte do schema (via `drift_postgres`, `PgTypes.timestampWithTimezone`).
|
|
5
|
+
- **Enums:** `textEnum<T>()` (legível e resistente a reordenação); enum nativo do Postgres só com tipo customizado.
|
|
6
|
+
- **JSON:** `jsonb` via `drift_postgres` quando precisar consultar dentro do JSON.
|
|
7
|
+
- **Upsert:** `insertOnConflictUpdate`/`onConflict` da API tipada; para casos avançados `customSelect('INSERT ... ON CONFLICT (...) DO UPDATE ... RETURNING ...', variables: [...])` com `$1`, `$2` ou `Variable`.
|
|
8
|
+
- **FK sem índice:** o Postgres não indexa automaticamente o lado filho; crie índice quando filtrar/joinear por ela.
|
|
9
|
+
- **Nomes:** o Drift converte `camelCase` em `snake_case` nas colunas; em SQL cru use o nome snake_case e o nome da tabela gerado.
|
|
10
|
+
- Sempre parametrize valores; nunca interpole strings em SQL.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Erros comuns
|
|
2
|
+
|
|
3
|
+
Se o projeto já faz algo desta lista **por convenção própria estável** (ex.: singleton global lido do ambiente, contagem por `get().length`), mantenha e apenas sinalize como sugestão — não como defeito.
|
|
4
|
+
|
|
5
|
+
| Erro | Correção |
|
|
6
|
+
|---|---|
|
|
7
|
+
| Concatenar entrada do usuário em SQL | Parâmetros (`Variable`, `:nome`, API tipada) |
|
|
8
|
+
| Repository retorna `Future` ou lança exceção | `TaskEither` + `tryCatch` |
|
|
9
|
+
| Classe de banco usada em controller/UI | Injetar repository/DAO |
|
|
10
|
+
| Abrir a mesma classe de banco várias vezes | Instância única injetada |
|
|
11
|
+
| Mudar tabela sem migration/`schemaVersion` | Migration versionada |
|
|
12
|
+
| Migration `stepByStep` usando tabelas atuais | Usar `schema.*` |
|
|
13
|
+
| Query sem `await` dentro de `transaction` | `await` em tudo |
|
|
14
|
+
| `getSingle` onde a linha pode não existir | `getSingleOrNull` |
|
|
15
|
+
| Listar sem paginar/ordenar | Paginação + `orderBy` estável |
|
|
16
|
+
| FK no SQLite sem `PRAGMA foreign_keys = ON` | Ligar no `beforeOpen` |
|
|
17
|
+
| Devolver `*Data`/`Companion` para o domínio | Mapear para modelo do domínio |
|
|
18
|
+
| Expor `error.toString()`/SQL ao cliente | Mensagem genérica; causa só em log |
|
|
19
|
+
| Credenciais fixas no código | Variáveis de ambiente/segredos |
|
|
20
|
+
| Esquecer o `build_runner` | Rodar após mudar tabelas/DAOs |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Checklist de revisão
|
|
2
|
+
|
|
3
|
+
- [ ] `fpdart` instalado; repository/service/controller retornam `TaskEither` e o banco está dentro de `TaskEither.tryCatch`?
|
|
4
|
+
- [ ] Estrutura existente do projeto preservada (pastas, migrations, singleton)?
|
|
5
|
+
- [ ] Classe de banco só dentro do repository/DAO, injetada; instância única?
|
|
6
|
+
- [ ] Nenhuma entrada do usuário concatenada em SQL; credenciais fora do código?
|
|
7
|
+
- [ ] Mensagem ao cliente sem SQL/causa original?
|
|
8
|
+
- [ ] Mudou schema? `schemaVersion` incrementado e migration escrita/testada?
|
|
9
|
+
- [ ] Mudou tabela/DAO/annotation? `build_runner` rodado?
|
|
10
|
+
- [ ] Defaults escolhidos corretamente (`clientDefault` × default no banco)?
|
|
11
|
+
- [ ] FKs com `onDelete` definido; SQLite com `PRAGMA foreign_keys = ON`?
|
|
12
|
+
- [ ] Listas paginadas (`page`/`pageSize`, `items/total/page/pageSize`) e ordenadas?
|
|
13
|
+
- [ ] Toda chamada em `transaction` com `await`?
|
|
14
|
+
- [ ] `getSingle` só onde a linha certamente existe?
|
|
15
|
+
- [ ] Testes com banco em memória; `close()` no `tearDown`?
|
|
16
|
+
- [ ] Dialeto correto (`build.yaml`, conexão, tipos)?
|
|
17
|
+
- [ ] Docs `///` nas APIs públicas?
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Classe de banco e conexão
|
|
2
|
+
|
|
3
|
+
## Classe de banco
|
|
4
|
+
|
|
5
|
+
- Uma `@DriftDatabase(tables: [...], daos: [...])` que estende `_$NomeDatabase`, com `part 'arquivo.g.dart';`.
|
|
6
|
+
- O construtor aceita um `QueryExecutor?` opcional para injetar banco em memória nos testes: `AppDatabase([QueryExecutor? e]) : super(e ?? _open());`.
|
|
7
|
+
- Pragmas e seed vão em `beforeOpen` (roda a cada abertura); condicione o seed com `details.wasCreated`.
|
|
8
|
+
- Feche com `await db.close()` no fim do ciclo de vida (testes, shutdown do servidor).
|
|
9
|
+
|
|
10
|
+
## Instância única e DI
|
|
11
|
+
|
|
12
|
+
- Uma instância por arquivo/banco. O mecanismo de DI é o do projeto: middleware (Dart Frog), construtor/factory, ou o container que o app já usa. Esta skill não impõe nenhum.
|
|
13
|
+
- Quem constrói o banco é a **raiz de composição** (ponto de entrada/middleware). Repositories recebem a classe de banco pelo construtor.
|
|
14
|
+
- **Projeto existente com singleton global** (ex.: `final db = AppDb(openConnection())` lido de variáveis de ambiente na importação): mantenha o padrão, não o trate como erro. Em código novo, prefira criar a instância na raiz de composição, o que facilita testes.
|
|
15
|
+
|
|
16
|
+
## Conexão fora da thread principal
|
|
17
|
+
|
|
18
|
+
- Flutter: `driftDatabase(name: ...)` (`drift_flutter`).
|
|
19
|
+
- Dart puro SQLite: `NativeDatabase.createInBackground(File(...))`.
|
|
20
|
+
- Postgres: `PgDatabase(...)`; veja `postgres/connection.md`.
|
|
21
|
+
|
|
22
|
+
Detalhes por dialeto: `sqlite/connection.md`, `postgres/connection.md`. Exemplos: `examples/database.dart`, `examples/connection_sqlite.dart`, `examples/connection_postgres.dart`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SQLite × Postgres
|
|
2
|
+
|
|
3
|
+
Os dois dialetos têm o mesmo peso. Identifique o do projeto (`pubspec.yaml`) e leia a pasta correspondente.
|
|
4
|
+
|
|
5
|
+
| Tema | SQLite | Postgres |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Pacotes | `sqlite3` / `drift_flutter` | `postgres`, `drift_postgres` |
|
|
8
|
+
| Conexão | arquivo local, isolate | rede, credenciais, SSL (`postgres/connection.md`) |
|
|
9
|
+
| `build.yaml` | padrão | `sql.dialects: [postgres]` |
|
|
10
|
+
| FKs | desligadas; `PRAGMA foreign_keys = ON` | sempre ativas |
|
|
11
|
+
| Tipos | tipagem dinâmica; `DateTime` inteiro/texto | tipos fortes; `timestamptz`, `uuid`, `jsonb` (`postgres/types-and-defaults.md`) |
|
|
12
|
+
| Defaults no banco | `currentDateAndTime`, `Constant` | também `gen_random_uuid()`, `now()` |
|
|
13
|
+
| Migrations | `ALTER TABLE` limitado (recriar tabela) | `ALTER TABLE` rico; DDL transacional (`postgres/migrations.md`) |
|
|
14
|
+
| Concorrência | um escritor; WAL | multiusuário; pool/locks |
|
|
15
|
+
| Testes | `NativeDatabase.memory()` | SQLite em memória p/ lógica geral; Postgres real (container) quando usar recursos do dialeto |
|
|
16
|
+
|
|
17
|
+
Código portável: evite SQL cru específico de dialeto no repository sem necessidade; quando usar, isole e comente.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Arquivos `.drift` (opcional)
|
|
2
|
+
|
|
3
|
+
Modo SQL-first do Drift: tabelas e queries escritas em SQL num arquivo `.drift`; o gerador **valida o SQL em tempo de build** e gera métodos Dart tipados.
|
|
4
|
+
|
|
5
|
+
## Quando usar
|
|
6
|
+
|
|
7
|
+
- Queries complexas (CTE, agregações, SQL específico) em que a API Dart fica verbosa.
|
|
8
|
+
- Preferível a `customSelect` quando precisar de SQL, pois é checado na compilação.
|
|
9
|
+
|
|
10
|
+
## Quando não usar
|
|
11
|
+
|
|
12
|
+
- Projeto existente que declara tudo em Dart: mantenha. Não misture estilos sem necessidade.
|
|
13
|
+
- CRUD simples: a API tipada basta.
|
|
14
|
+
|
|
15
|
+
## Como
|
|
16
|
+
|
|
17
|
+
- Inclua os arquivos em `@DriftDatabase(include: {'queries.drift'})`.
|
|
18
|
+
- Nomeie cada query (`findByOwner: SELECT * FROM orders WHERE owner_id = :ownerId;`); parâmetros nomeados evitam concatenação.
|
|
19
|
+
- Em Postgres, o dialeto precisa estar em `build.yaml` (`references/setup.md`).
|
|
20
|
+
- O repository chama o método gerado dentro de `TaskEither.tryCatch`, como qualquer outro acesso.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Migrations
|
|
2
|
+
|
|
3
|
+
## Aviso: projeto existente
|
|
4
|
+
|
|
5
|
+
Se o projeto **já tem** migrations (manuais, `if (from < n)`, `customStatement`, sem snapshots), **mantenha o estilo dele**: não migre para `stepByStep`/`make-migrations` sem pedido. Aplique as demais regras, em especial segurança e idempotência. O fluxo abaixo é o padrão DS9 para **projetos novos**.
|
|
6
|
+
|
|
7
|
+
## Regras gerais (qualquer estilo)
|
|
8
|
+
|
|
9
|
+
- Mudou o schema → incremente `schemaVersion` e escreva a migration. O Drift não altera bancos existentes.
|
|
10
|
+
- DDL idempotente quando possível (`IF NOT EXISTS`, `ADD COLUMN IF NOT EXISTS` em Postgres).
|
|
11
|
+
- Migration nunca apaga dados sem backup/confirmação explícita; renomear = criar + copiar + remover.
|
|
12
|
+
- SQL em `customStatement` é fixo (sem entrada do usuário).
|
|
13
|
+
- Evite queries de dados em callbacks de migration, salvo após o schema chegar ao estado esperado.
|
|
14
|
+
- `beforeOpen`: pragmas e seed; use `details.wasCreated`/`details.hadUpgrade`. Não coloque lógica pesada.
|
|
15
|
+
- Teste a migração partindo de um banco da versão anterior.
|
|
16
|
+
|
|
17
|
+
## Padrão DS9 para projetos novos: guiado
|
|
18
|
+
|
|
19
|
+
1. Registre o banco em `databases:` no `build.yaml` e rode `dart run drift_dev make-migrations` **antes** da primeira mudança (salva o schema inicial em `drift_schemas/`).
|
|
20
|
+
2. A cada mudança: incremente `schemaVersion`, rode `make-migrations` de novo e escreva o passo `fromXToY`.
|
|
21
|
+
3. Escreva com `stepByStep(from1To2: ..., from2To3: ...)` usando o `schema` recebido, **nunca** as tabelas atuais da classe. Extraia para um getter de extensão em `GeneratedDatabase`.
|
|
22
|
+
4. Rode os testes de migration gerados (`test/drift/`) e versione `drift_schemas/`.
|
|
23
|
+
|
|
24
|
+
Exemplos: `examples/migration_step_by_step.dart` (guiado), `examples/migration_manual.dart` (estilo manual de projetos existentes).
|
|
25
|
+
|
|
26
|
+
Postgres: `postgres/migrations.md`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
- Índices em colunas muito filtradas/ordenadas e em FKs consultadas (`@TableIndex`).
|
|
4
|
+
- Pagine sempre listas que podem crescer; ordene de forma estável.
|
|
5
|
+
- Evite N+1: não faça uma query por item de um loop; use join ou `isIn` com lista de ids.
|
|
6
|
+
- Use `batch((b) { b.insertAll(...); })` para muitos inserts.
|
|
7
|
+
- Prefira `count()` a carregar linhas só para contar (quando o projeto permitir).
|
|
8
|
+
- `watch()` só onde a UI precisa de reatividade; cada stream mantém uma query viva.
|
|
9
|
+
- Selecione apenas colunas necessárias em listas grandes (`addColumns`/`selectOnly`).
|
|
10
|
+
- Postgres: use `EXPLAIN ANALYZE` em queries lentas; considere índices parciais e pool de conexões.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Queries e paginação
|
|
2
|
+
|
|
3
|
+
## API
|
|
4
|
+
|
|
5
|
+
- Prefira a API tipada (`select`, `into`, `update`, `delete`, joins) a SQL cru.
|
|
6
|
+
- SQL cru (`customSelect`/`customStatement`) só quando necessário (ex.: upsert `ON CONFLICT ... RETURNING`); sempre com `variables:` parametrizadas; `customSelect` precisa de `readsFrom` para funcionar com `watch`.
|
|
7
|
+
- Insert: `Companion.insert(...)` (só campos sem default são obrigatórios). Update parcial: `Companion(campo: Value(x))`.
|
|
8
|
+
- `get()` para leitura única; `watch()` para UI reativa (já emite o valor atual; não combine com `get()`).
|
|
9
|
+
- `getSingle()` lança se não houver exatamente uma linha; para ausência válida use `getSingleOrNull()`.
|
|
10
|
+
- Join: `select(a).join([innerJoin(b, b.id.equalsExp(a.bId))])`; leia com `row.readTable(a)`.
|
|
11
|
+
- Ordene sempre que paginar, com desempate estável (ex.: `createdAt` e depois `id`).
|
|
12
|
+
|
|
13
|
+
## Paginação (padrão DS9)
|
|
14
|
+
|
|
15
|
+
Entrada e saída:
|
|
16
|
+
|
|
17
|
+
```dart
|
|
18
|
+
/// Pedido de paginação.
|
|
19
|
+
class PaginationRequest {
|
|
20
|
+
const PaginationRequest({required this.page, required this.pageSize});
|
|
21
|
+
final int page; // base 1
|
|
22
|
+
final int pageSize;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Resultado paginado.
|
|
26
|
+
class PaginateResponse<T> {
|
|
27
|
+
PaginateResponse({required this.items, required this.total, required this.page, required this.pageSize});
|
|
28
|
+
final List<T> items;
|
|
29
|
+
final int total;
|
|
30
|
+
final int page;
|
|
31
|
+
final int pageSize;
|
|
32
|
+
int get totalPages => (total / pageSize).ceil();
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- Defaults ao ler a query string: `page = 1`, `pageSize = 10`.
|
|
37
|
+
- Offset: `(page - 1) * pageSize`; `limit(pageSize, offset: offset)`.
|
|
38
|
+
- Total: consulta da mesma base de filtros, sem `limit`. O padrão do projeto de referência calcula o total carregando as linhas (`base.get().then((r) => r.length)`); prefira, quando o projeto permitir, `count()` (`table.id.count()` com `addColumns`) para não trazer todas as linhas. **Em projeto existente, mantenha o que ele já usa.**
|
|
39
|
+
- Valide e limite `page >= 1` e `pageSize` entre 1 e um máximo (ex.: 100) na borda (controller/DTO) para evitar consultas gigantes.
|
|
40
|
+
- O repository devolve `TaskEither<BusinessException, PaginateResponse<T>>`.
|
|
41
|
+
|
|
42
|
+
Exemplo: `examples/pagination.dart`.
|
|
43
|
+
|
|
44
|
+
## Agregações
|
|
45
|
+
|
|
46
|
+
- `table.id.count()`, `sum()`, `avg()`, `max()`, `min()` com `addColumns([...])` e `groupBy`. Filtros condicionais com `Expression.and`/`where`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Repository / DAO com `TaskEither`
|
|
2
|
+
|
|
3
|
+
## Regra
|
|
4
|
+
|
|
5
|
+
Toda camada (controller, service, repository) retorna `TaskEither<Falha, T>`. No repository, a falha é `BusinessException` (camada de erro de persistência/negócio, veja `hex-pattern-dart`/`dart-frog`).
|
|
6
|
+
|
|
7
|
+
- Envolva **todo** acesso ao banco em `TaskEither.tryCatch(() async {...}, (error, stackTrace) => BusinessException(...))`.
|
|
8
|
+
- Nunca deixe `Future` com exceção sair do repository; nunca converta falha em sucesso vazio.
|
|
9
|
+
- O `.run()` acontece só na borda (rota/handler/UI).
|
|
10
|
+
- A causa original (`error`, `stackTrace`) vai só para log; a mensagem ao cliente é genérica.
|
|
11
|
+
- Entidade ausente: modele no tipo (`Order?` no `Right`) e deixe o service decidir (404), em vez de lançar.
|
|
12
|
+
|
|
13
|
+
## Port × repository
|
|
14
|
+
|
|
15
|
+
- O **port** (`OrderRepositoryPort`) fica no domínio, sem import de `drift`.
|
|
16
|
+
- O **repository** (`OrderRepository implements OrderRepositoryPort`) fica no adapter e usa a classe de banco injetada.
|
|
17
|
+
- Converta linhas do Drift em **modelos do domínio** dentro do repository; não vaze `*Data`/`Companion` para fora.
|
|
18
|
+
|
|
19
|
+
## DAO × repository
|
|
20
|
+
|
|
21
|
+
- Com DAOs (`@DriftAccessor`), o repository delega ao DAO dentro do `tryCatch`; o DAO retorna `Future`/`Stream` (camada interna de acesso).
|
|
22
|
+
- Sem DAOs (comum em projetos existentes), o repository usa a classe de banco direto. Mantenha o que o projeto usa.
|
|
23
|
+
- Streams (`watch`) em apps Flutter: o repository expõe `Stream` apenas quando a camada de UI precisa reagir; as operações pontuais continuam `TaskEither`.
|
|
24
|
+
|
|
25
|
+
## Transação dentro do repository
|
|
26
|
+
|
|
27
|
+
Veja `references/transactions.md`: a transação inteira fica dentro de um único `tryCatch`.
|
|
28
|
+
|
|
29
|
+
Exemplos: `examples/repository_hex.dart`, `examples/pagination.dart`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Setup e geração de código
|
|
2
|
+
|
|
3
|
+
## Dependências
|
|
4
|
+
|
|
5
|
+
| Alvo | `dependencies` | `dev_dependencies` |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Flutter (SQLite) | `drift`, `drift_flutter`, `path_provider` | `drift_dev`, `build_runner` |
|
|
8
|
+
| Dart puro (SQLite) | `drift`, `sqlite3` | `drift_dev`, `build_runner` |
|
|
9
|
+
| Dart puro (Postgres) | `drift`, `postgres`, `drift_postgres` | `drift_dev`, `build_runner` |
|
|
10
|
+
| Todos (padrão DS9) | `fpdart` | — |
|
|
11
|
+
|
|
12
|
+
- `fpdart` é obrigatório no padrão DS9 (`TaskEither`). Se não estiver instalado, avise e peça para instalar.
|
|
13
|
+
- Mantenha as versões já usadas pelo projeto; só sugira upgrade se pedido.
|
|
14
|
+
|
|
15
|
+
## Geração
|
|
16
|
+
|
|
17
|
+
- Uma vez: `dart run build_runner build`.
|
|
18
|
+
- Em desenvolvimento: `dart run build_runner watch`.
|
|
19
|
+
- Se houver conflito de saídas: `dart run build_runner build --delete-conflicting-outputs`.
|
|
20
|
+
- Versione ou não os `*.g.dart` conforme a convenção do projeto; não mude isso por conta própria.
|
|
21
|
+
|
|
22
|
+
## `build.yaml`
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
targets:
|
|
26
|
+
$default:
|
|
27
|
+
builders:
|
|
28
|
+
drift_dev:
|
|
29
|
+
options:
|
|
30
|
+
# Postgres: gera SQL do dialeto correto.
|
|
31
|
+
sql:
|
|
32
|
+
dialects: [postgres] # SQLite: [sqlite]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- Por padrão o gerador só emite SQL de SQLite; em Postgres, declare o dialeto.
|
|
36
|
+
- Para projetos novos em SQLite, considere `store_date_time_values_as_text: true` (ISO-8601 com fuso). Em projeto existente, **não mude**: altera a leitura dos dados já gravados.
|
|
37
|
+
- Para migrations guiadas, registre o banco em `databases:` (veja `references/migrations.md`).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Tabelas e colunas
|
|
2
|
+
|
|
3
|
+
- Tabela = classe que estende `Table`; colunas como `late final campo = tipo()();` (parênteses finais obrigatórios) ou `TextColumn get campo => text()();`. Siga o estilo já usado no projeto.
|
|
4
|
+
- Colunas são não-nulas por padrão; use `.nullable()` só quando `null` tiver significado de domínio.
|
|
5
|
+
- Documente a tabela e cada coluna com `///`.
|
|
6
|
+
|
|
7
|
+
## Chave primária
|
|
8
|
+
|
|
9
|
+
- Inteiro: `integer().autoIncrement()()`.
|
|
10
|
+
- Outros casos: sobrescreva `Set<Column> get primaryKey => {id};` (só com `=>` e literal de set).
|
|
11
|
+
- UUID: gere no cliente (`clientDefault`) ou no banco (veja defaults).
|
|
12
|
+
|
|
13
|
+
## Defaults: cliente × banco
|
|
14
|
+
|
|
15
|
+
| Situação | Use |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Valor calculado em Dart (`DateTime.now`, uuid do app), sem exigir migration | `clientDefault(() => ...)` — padrão para código novo |
|
|
18
|
+
| Default que é **parte do schema** e deve valer também para inserts fora do app (SQL, outros serviços) | `withDefault(...)` — ex.: `gen_random_uuid()`, `currentDateAndTime`, `Constant(false)` |
|
|
19
|
+
| Projeto existente já usa `withDefault` | mantenha |
|
|
20
|
+
|
|
21
|
+
`withDefault` altera o schema (exige migration ao adicionar/mudar); `clientDefault` não. Não há conflito: escolha pelo critério acima. Em Postgres, `gen_random_uuid()` no banco é legítimo (`postgres/types-and-defaults.md`).
|
|
22
|
+
|
|
23
|
+
## Chaves estrangeiras
|
|
24
|
+
|
|
25
|
+
- `.references(Tabela, #coluna, onDelete: KeyAction.cascade)`; defina `onDelete` conscientemente (`cascade`, `setNull`, `restrict`).
|
|
26
|
+
- Auto-referência (`replyFrom` → mesma tabela) é permitida; use `@ReferenceName` quando houver mais de uma FK para a mesma tabela.
|
|
27
|
+
- SQLite: FKs vêm desligadas; ligue com `PRAGMA foreign_keys = ON` no `beforeOpen`.
|
|
28
|
+
|
|
29
|
+
## Integridade e índices
|
|
30
|
+
|
|
31
|
+
- `.unique()`, `uniqueKeys` para unicidade composta, `.check(expr)` para validação (prefira `check` a `withLength`, que só valida em Dart).
|
|
32
|
+
- `@TableIndex(name: ..., columns: {#col})` para colunas muito filtradas. PK, `unique` e FK já têm índice (no Postgres a FK **não** cria índice automático no lado filho; indexe se filtrar por ela).
|
|
33
|
+
|
|
34
|
+
## Enums e tipos próprios
|
|
35
|
+
|
|
36
|
+
- `intEnum<T>()` ou `textEnum<T>()`; ou `TypeConverter`. Prefira `textEnum` quando a ordem do enum pode mudar (inteiro quebra ao reordenar).
|
|
37
|
+
|
|
38
|
+
## Reuso
|
|
39
|
+
|
|
40
|
+
- Mixins de tabela para colunas comuns (`id`, `createdAt`, `deletedAt`).
|
|
41
|
+
- Soft delete: coluna `deleted` + `deletedAt`; filtre nas queries do repository, não na UI.
|
|
42
|
+
|
|
43
|
+
Exemplo: `examples/table.dart`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Testes
|
|
2
|
+
|
|
3
|
+
- Repositories/DAOs: banco em memória — `AppDatabase(NativeDatabase.memory())`; abra no `setUp`, feche no `tearDown`.
|
|
4
|
+
- Flutter widget tests: `DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)`.
|
|
5
|
+
- Repository retorna `TaskEither`: execute com `.run()` e inspecione o `Either` (`isRight()`, `match`).
|
|
6
|
+
- Cubra: caminho feliz, ausência (`null`), violação de unicidade/FK (vira `Left(BusinessException)`), paginação (página 1, última, vazia), transação com rollback.
|
|
7
|
+
- Services: use um fake do **port** (sem Drift); o Drift só aparece nos testes do repository.
|
|
8
|
+
- Migrations: use os testes gerados por `make-migrations` (`test/drift/`); em estilo manual, crie banco na versão anterior, rode a migration e verifique o schema/dados.
|
|
9
|
+
- Postgres: lógica genérica pode rodar em SQLite em memória; recursos do dialeto (`gen_random_uuid`, `jsonb`, `ON CONFLICT`) exigem Postgres real (container) em teste de integração.
|
|
10
|
+
- Delegue a escrita/execução ao agente `test-writer`.
|
|
11
|
+
|
|
12
|
+
Exemplo: `examples/test.dart`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Transações
|
|
2
|
+
|
|
3
|
+
- Operações atômicas vão em `transaction(() async { ... })`.
|
|
4
|
+
- Faça `await` em **todas** as chamadas dentro da transação. Query sem `await` pode rodar após o fechamento e causar perda de dados ou crash. Não agende timers que usem o banco dentro dela.
|
|
5
|
+
- Streams criados fora da transação só notificam no commit; não escute fora um stream criado dentro.
|
|
6
|
+
- Se a transação lançar, ela faz rollback. No repository, envolva a transação inteira em um único `TaskEither.tryCatch`, para que a falha vire `Left(BusinessException)`.
|
|
7
|
+
- Evite chamadas de rede dentro da transação (segura conexão e locks).
|
|
8
|
+
- Em Postgres, transações longas seguram locks; mantenha curtas. Isolation level e `SELECT ... FOR UPDATE` exigem SQL cru parametrizado (`postgres/types-and-defaults.md`).
|
|
9
|
+
|
|
10
|
+
```dart
|
|
11
|
+
TaskEither<BusinessException, void> moveItems(String from, String to) =>
|
|
12
|
+
TaskEither.tryCatch(
|
|
13
|
+
() => _db.transaction(() async {
|
|
14
|
+
await (_db.update(_db.itemTable)..where((t) => t.listId.equals(from)))
|
|
15
|
+
.write(ItemTableCompanion(listId: Value(to)));
|
|
16
|
+
await (_db.delete(_db.listTable)..where((t) => t.id.equals(from))).go();
|
|
17
|
+
}),
|
|
18
|
+
(e, s) => BusinessException(
|
|
19
|
+
domainName: 'list', statusCode: 500, message: 'Falha ao mover itens.',
|
|
20
|
+
error: e, stackTrace: s,
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SQLite — conexão
|
|
2
|
+
|
|
3
|
+
- Flutter: `driftDatabase(name: 'app', native: DriftNativeOptions(databaseDirectory: getApplicationSupportDirectory))` (`drift_flutter`).
|
|
4
|
+
- Dart puro: `NativeDatabase.createInBackground(File(path))` — roda em isolate, fora da thread principal.
|
|
5
|
+
- Testes: `NativeDatabase.memory()`.
|
|
6
|
+
- Ligue FKs no `beforeOpen`: `await customStatement('PRAGMA foreign_keys = ON');`.
|
|
7
|
+
- Considere WAL (`PRAGMA journal_mode = WAL`) para leitura concorrente em apps com muita escrita.
|
|
8
|
+
- Uma instância por arquivo; várias instâncias sobre o mesmo arquivo quebram streams.
|
|
9
|
+
- Web: requer configuração específica (WASM); siga a documentação oficial.
|
|
10
|
+
|
|
11
|
+
Exemplo: `examples/connection_sqlite.dart`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SQLite — tipos e pragmas
|
|
2
|
+
|
|
3
|
+
- **DateTime:** por padrão é inteiro Unix (segundos, sem fuso). Em projeto **novo**, considere `store_date_time_values_as_text: true` (ISO-8601). Em projeto existente, **não mude**.
|
|
4
|
+
- **Tipagem dinâmica:** o SQLite aceita valor de tipo diferente; confie na API tipada do Drift, não em SQL cru.
|
|
5
|
+
- **Defaults:** `clientDefault` por padrão; `withDefault(currentDateAndTime)`/`Constant(...)` quando o default for parte do schema.
|
|
6
|
+
- **ALTER TABLE limitado:** renomear/remover/alterar coluna pode exigir recriar a tabela; no fluxo guiado, `m.alterTable(TableMigration(...))`.
|
|
7
|
+
- **Pragmas** em `beforeOpen`: `foreign_keys = ON`; opcional `journal_mode = WAL`.
|
|
8
|
+
- **Concorrência:** um escritor por vez; mantenha transações curtas.
|
|
9
|
+
- **Chave primária:** `integer().autoIncrement()()` é o caso comum.
|
|
@@ -417,3 +417,22 @@ void main() {
|
|
|
417
417
|
- Testes no Flutter: https://docs.flutter.dev/testing/overview
|
|
418
418
|
- Effective Dart: https://dart.dev/effective-dart
|
|
419
419
|
- Skills oficiais do Flutter: https://github.com/flutter/skills
|
|
420
|
+
|
|
421
|
+
## Registro de atividade (obrigatório)
|
|
422
|
+
|
|
423
|
+
Siga o **padrão de registro DS9** (o mesmo da skill `debugger`): ao aplicar estas diretrizes em uma tarefa (features, providers, services, views, testes), **registre na conversa, em tempo real, tudo o que consome contexto ou tokens**, em uma linha curta por item, antes ou logo depois da ação. O registro é só texto na resposta: **não grave arquivo de log**.
|
|
424
|
+
|
|
425
|
+
| Tipo | Registre |
|
|
426
|
+
|---|---|
|
|
427
|
+
| Arquivo lido | caminho e trecho (`src/a.ts:10-60`) ou "inteiro" |
|
|
428
|
+
| Skill/reference | `skill:<id>`, `get_skill_file <id> references/x.md` |
|
|
429
|
+
| Busca | padrão e pasta (`grep "X" src/`) |
|
|
430
|
+
| Comando | o comando e o tamanho aproximado da saída (curta/média/longa); saída longa é filtrada ou resumida |
|
|
431
|
+
| Web/MCP | URL ou tool chamada (`Referência externa: <nome/link>` no fallback web) |
|
|
432
|
+
| Subagente | nome e objetivo |
|
|
433
|
+
| Log/stack colado | origem e tamanho aproximado |
|
|
434
|
+
| Arquivo criado/alterado | caminho e motivo (`✏️ Criando: <caminho>` / `✏️ Atualizando: <caminho>`); só quando a alteração for permitida |
|
|
435
|
+
|
|
436
|
+
Formato: `📄 leitura · 🧩 skill · 🔎 busca · ⚙️ comando · 🌐 web/MCP · 🤖 subagente · ✏️ escrita`, por exemplo `📄 lib/orders/order_service.dart:30-90` ou `🧩 get_skill_file <id> references/x.md`.
|
|
437
|
+
|
|
438
|
+
Regras: leia o mínimo necessário (trechos em vez de arquivos inteiros, filtros em saídas de comando), não releia o que já leu e mascare segredos no registro. Ao final, inclua o resumo **"Consumo da tarefa"**: tabela com tipo, itens e observação, apontando o que foi mais caro e como reduzir.
|
|
@@ -81,3 +81,24 @@ Use `list_skill_files` e `get_skill_file` com `skill: hex-pattern-dart`.
|
|
|
81
81
|
| Árvore de pastas de exemplo | `examples/structure-tree.md` |
|
|
82
82
|
| Port (`<Papel>Port`), service e adapters (pseudocódigo) | `examples/port.md`, `examples/service.md`, `examples/adapter-in.md`, `examples/adapter-out.md` |
|
|
83
83
|
| Hierarquia de exceções | `examples/errors.md` |
|
|
84
|
+
|
|
85
|
+
## Registro de atividade (obrigatório)
|
|
86
|
+
|
|
87
|
+
Siga o **padrão de registro DS9** (o mesmo da skill `debugger`): ao aplicar este padrão em uma tarefa (camadas, ports, adapters, erros), **registre na conversa, em tempo real, tudo o que consome contexto ou tokens**, em uma linha curta por item, antes ou logo depois da ação. O registro é só texto na resposta: **não grave arquivo de log**.
|
|
88
|
+
|
|
89
|
+
| Tipo | Registre |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Arquivo lido | caminho e trecho (`src/a.ts:10-60`) ou "inteiro" |
|
|
92
|
+
| Skill/reference | `skill:<id>`, `get_skill_file <id> references/x.md` |
|
|
93
|
+
| Busca | padrão e pasta (`grep "X" src/`) |
|
|
94
|
+
| Comando | o comando e o tamanho aproximado da saída (curta/média/longa); saída longa é filtrada ou resumida |
|
|
95
|
+
| Web/MCP | URL ou tool chamada (`Referência externa: <nome/link>` no fallback web) |
|
|
96
|
+
| Subagente | nome e objetivo |
|
|
97
|
+
| Log/stack colado | origem e tamanho aproximado |
|
|
98
|
+
| Arquivo criado/alterado | caminho e motivo (`✏️ Criando: <caminho>` / `✏️ Atualizando: <caminho>`); só quando a alteração for permitida |
|
|
99
|
+
|
|
100
|
+
Formato: `📄 leitura · 🧩 skill · 🔎 busca · ⚙️ comando · 🌐 web/MCP · 🤖 subagente · ✏️ escrita`, por exemplo `📄 lib/orders/order_service.dart:30-90` ou `🧩 get_skill_file <id> references/x.md`.
|
|
101
|
+
|
|
102
|
+
Registre também cada arquivo de `references/` ou `examples/` consultado.
|
|
103
|
+
|
|
104
|
+
Regras: leia o mínimo necessário (trechos em vez de arquivos inteiros, filtros em saídas de comando), não releia o que já leu e mascare segredos no registro. Ao final, inclua o resumo **"Consumo da tarefa"**: tabela com tipo, itens e observação, apontando o que foi mais caro e como reduzir.
|
|
@@ -8,13 +8,12 @@ class OrderController
|
|
|
8
8
|
|
|
9
9
|
OrderController(this._service)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
TaskEither<ClientException, OrderResponse> getById(Context context, String id)
|
|
12
12
|
return _service.getById(id, context.requesterId)
|
|
13
13
|
.mapLeft(toClientException) // BusinessException -> ClientException
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
14
|
+
.map(OrderResponse.from) // modelo -> DTO
|
|
15
|
+
|
|
16
|
+
// A rota (borda) executa .run() e converte Left/Right em Response.
|
|
18
17
|
|
|
19
18
|
toClientException(BusinessException e)
|
|
20
19
|
safe = e.statusCode >= 500 ? "Erro interno" : e.message // 5xx genérico
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Persistência agnóstica
|
|
2
2
|
|
|
3
|
-
O padrão não prescreve banco nem ORM. Para a tecnologia do projeto, consulte a skill correspondente via `list_skills`/`recommend_for_task` e preserve o que já existe.
|
|
3
|
+
O padrão não prescreve banco nem ORM. Para a tecnologia do projeto, consulte a skill correspondente via `list_skills`/`recommend_for_task` (ex.: `driftorm` para Drift) e preserve o que já existe.
|
|
4
4
|
|
|
5
5
|
## Contrato
|
|
6
6
|
|
|
@@ -18,6 +18,6 @@ O padrão não prescreve banco nem ORM. Para a tecnologia do projeto, consulte a
|
|
|
18
18
|
|
|
19
19
|
1. Toda chamada de I/O dentro de `TaskEither.tryCatch`.
|
|
20
20
|
2. Falha vira `BusinessException` com mensagem segura.
|
|
21
|
-
3. "Não encontrado" é resultado explícito (`left` com status adequado ou `
|
|
21
|
+
3. "Não encontrado" é resultado explícito (`left` com status adequado, `Option` ou tipo anulável explícito no `Right`), nunca `null` implícito.
|
|
22
22
|
4. Transação: abra no adapter; o service expõe a operação de negócio completa, sem conhecer a transação.
|
|
23
23
|
5. Migrations seguem a skill de persistência do projeto e não alteram estrutura existente sem pedido.
|
package/package.json
CHANGED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: docker
|
|
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
|
-
---
|
|
5
|
-
|
|
6
|
-
# Docker — Diretrizes DS9
|
|
7
|
-
|
|
8
|
-
Convenções para containerizar aplicações Node.js e seus serviços de apoio (MySQL, etc.) nos projetos da empresa.
|
|
9
|
-
|
|
10
|
-
## Dockerfile multi-stage (obrigatório)
|
|
11
|
-
|
|
12
|
-
Toda aplicação Node.js usa build multi-stage: uma etapa de build (com devDependencies e compilação TypeScript) e uma etapa final enxuta só com o necessário para rodar.
|
|
13
|
-
|
|
14
|
-
```dockerfile
|
|
15
|
-
# ---- deps & build ----
|
|
16
|
-
FROM node:20-alpine AS build
|
|
17
|
-
WORKDIR /app
|
|
18
|
-
COPY package.json package-lock.json ./
|
|
19
|
-
RUN npm ci
|
|
20
|
-
COPY . .
|
|
21
|
-
RUN npm run build
|
|
22
|
-
|
|
23
|
-
# ---- runtime ----
|
|
24
|
-
FROM node:20-alpine AS runtime
|
|
25
|
-
WORKDIR /app
|
|
26
|
-
ENV NODE_ENV=production
|
|
27
|
-
COPY package.json package-lock.json ./
|
|
28
|
-
RUN npm ci --omit=dev
|
|
29
|
-
COPY --from=build /app/dist ./dist
|
|
30
|
-
USER node
|
|
31
|
-
EXPOSE 3000
|
|
32
|
-
CMD ["node", "dist/main.js"]
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Regras obrigatórias:
|
|
36
|
-
|
|
37
|
-
- Usar imagem base fixada por tag específica (ex.: `node:20-alpine`), nunca `latest`.
|
|
38
|
-
- `npm ci` (não `npm install`) para builds reprodutíveis a partir do `package-lock.json`.
|
|
39
|
-
- A imagem final **não** contém devDependencies, código-fonte TypeScript, nem ferramentas de build — só `dist/` (ou equivalente) e `node_modules` de produção.
|
|
40
|
-
- Rodar como usuário não-root (`USER node`, já disponível na imagem oficial), nunca `root` em produção.
|
|
41
|
-
- `.dockerignore` sempre presente, excluindo `node_modules`, `.git`, `.env*`, arquivos de teste e artefatos locais.
|
|
42
|
-
|
|
43
|
-
## Variáveis de ambiente e segredos
|
|
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 (use o mecanismo de configuração do projeto).
|
|
46
|
-
- `.env` usado em desenvolvimento local nunca é commitado (deve estar no `.gitignore`) nem copiado para a imagem (`.dockerignore`).
|
|
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
|
-
|
|
49
|
-
## docker-compose para ambiente local
|
|
50
|
-
|
|
51
|
-
- Um serviço por responsabilidade (`app`, `mysql`, eventual `redis`/fila), nunca múltiplos processos no mesmo container.
|
|
52
|
-
- Serviço de banco (`mysql`) usa volume nomeado para persistir dados entre restarts (`volumes: db_data:/var/lib/mysql`) e `healthcheck` para que a aplicação só suba depois do banco pronto (`depends_on: condition: service_healthy`).
|
|
53
|
-
- Exemplo mínimo:
|
|
54
|
-
|
|
55
|
-
```yaml
|
|
56
|
-
services:
|
|
57
|
-
app:
|
|
58
|
-
build: .
|
|
59
|
-
env_file: .env
|
|
60
|
-
ports: ["3000:3000"]
|
|
61
|
-
depends_on:
|
|
62
|
-
mysql:
|
|
63
|
-
condition: service_healthy
|
|
64
|
-
|
|
65
|
-
mysql:
|
|
66
|
-
image: mysql:8.0
|
|
67
|
-
environment:
|
|
68
|
-
MYSQL_DATABASE: app_db
|
|
69
|
-
volumes: ["db_data:/var/lib/mysql"]
|
|
70
|
-
healthcheck:
|
|
71
|
-
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
72
|
-
interval: 5s
|
|
73
|
-
timeout: 5s
|
|
74
|
-
retries: 10
|
|
75
|
-
|
|
76
|
-
volumes:
|
|
77
|
-
db_data:
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Tamanho e cache de imagem
|
|
81
|
-
|
|
82
|
-
- Ordene as instruções do Dockerfile das que mudam menos (dependências) para as que mudam mais (código-fonte), copiando `package.json`/`package-lock.json` antes do restante do código para aproveitar cache de camadas do `npm ci`.
|
|
83
|
-
- Prefira imagens `-alpine`/`-slim` para reduzir superfície de ataque e tamanho, validando compatibilidade de dependências nativas (ex.: pacotes com binários compilados) antes de adotar.
|
|
84
|
-
|
|
85
|
-
## Segurança de imagem
|
|
86
|
-
|
|
87
|
-
- Rode scan de vulnerabilidade (ex.: `docker scout`/Trivy, conforme ferramenta padrão do CI da empresa) na imagem final antes de publicar em produção.
|
|
88
|
-
- Nunca exponha a porta de debug/inspeção do Node (`--inspect`) em imagens de produção.
|
|
89
|
-
- Fixe versões de imagens base por tag (idealmente com digest) para builds reprodutíveis e auditáveis.
|
|
90
|
-
|
|
91
|
-
## Checklist ao criar/revisar Dockerfile/compose
|
|
92
|
-
|
|
93
|
-
1. Build é multi-stage e a imagem final não tem devDependencies/código-fonte?
|
|
94
|
-
2. Container roda como usuário não-root?
|
|
95
|
-
3. Nenhum segredo está hardcoded no Dockerfile ou no compose versionado?
|
|
96
|
-
4. `.dockerignore` existe e exclui `node_modules`, `.git`, `.env*`?
|
|
97
|
-
5. Serviços dependentes (banco) têm `healthcheck` e `depends_on` configurados corretamente?
|
|
98
|
-
|
|
99
|
-
## Não fazer
|
|
100
|
-
|
|
101
|
-
- Não usar `FROM node:latest` ou qualquer tag flutuante em produção.
|
|
102
|
-
- Não rodar o processo da aplicação como `root`.
|
|
103
|
-
- Não commitar `.env` com valores reais nem copiá-lo para dentro da imagem.
|
|
104
|
-
- Não usar `npm install` no build de imagem (usar `npm ci`).
|