jarvis-ai-framework 1.0.3 → 2.0.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/AGENTS.md +66 -64
- package/README.md +24 -10
- package/agents/AGENTS.md +12 -8
- package/agents/README.md +14 -16
- package/agents/engineering/data/eng.data-engineer.agent.md +6 -6
- package/agents/engineering/eng.agent.md +19 -36
- package/agents/engineering/eng.bug-hunter.md +13 -12
- package/agents/engineering/eng.cybersecurity.agent.md +20 -35
- package/agents/engineering/eng.docs-writer.md +2 -2
- package/agents/engineering/eng.frontend.agent.md +17 -22
- package/agents/engineering/eng.rpa.agent.md +5 -15
- package/agents/engineering/qa/eng.qa.test-architect.md +4 -4
- package/agents/engineering/qa/eng.qa.test-planner.md +3 -3
- package/agents/engineering/qa/eng.qa.testing-engineer.md +3 -3
- package/agents/product/prod.discovery-interviewer.md +86 -0
- package/bin/commands/init.js +6 -6
- package/bin/jarvis.js +1 -1
- package/bin/lib/config/constants.js +47 -0
- package/bin/lib/core/sync-engine.js +12 -0
- package/package.json +2 -5
- package/rules/AGENTS.md +6 -4
- package/rules/engineering/eng-rules.md +5 -3
- package/rules/engineering/eng-security-rules.md +4 -4
- package/rules/engineering/eng.breakdown-subtasks-rules.md +208 -37
- package/rules/engineering/eng.docs-scraping-rules.md +1 -1
- package/rules/engineering/eng.downstream-flow-rules.md +1 -1
- package/rules/engineering/eng.integrations-rules.md +2 -2
- package/rules/engineering/eng.pre-pr-rules.md +1 -1
- package/rules/engineering/eng.skills-rules.md +60 -0
- package/rules/engineering/eng.specializations-rules.md +69 -0
- package/rules/engineering/eng.start-rules.md +11 -11
- package/rules/engineering/eng.work-rules.md +19 -14
- package/rules/engineering/frontend/eng.frontend-rules.md +70 -69
- package/rules/product/README.md +2 -0
- package/rules/product/prod-rules.md +7 -5
- package/{skills/prod-specs/rules/prod-spec-rules.md → rules/product/prod.spec-rules.md} +55 -50
- package/skills/AGENTS.md +111 -47
- package/skills/SKILLS-ROADMAP.md +26 -45
- package/skills/eng-ai-engineer/SKILL.md +1 -0
- package/skills/eng-backend/SKILL.md +142 -644
- package/skills/eng-backend/references/1-fundamentos-programacao.md +41 -0
- package/skills/eng-backend/references/10-seguranca.md +61 -0
- package/skills/eng-backend/references/11-performance.md +49 -0
- package/skills/eng-backend/references/12-erros-logging.md +42 -0
- package/skills/eng-backend/references/13-qualidade-codigo.md +51 -0
- package/skills/eng-backend/references/2-design-arquitetura.md +48 -0
- package/skills/eng-backend/references/3-design-patterns.md +50 -0
- package/skills/eng-backend/references/4-algoritmos-estruturas.md +43 -0
- package/skills/eng-backend/references/5-banco-dados.md +53 -0
- package/skills/eng-backend/references/6-api-comunicacao.md +42 -0
- package/skills/eng-backend/references/7-concorrencia-async.md +49 -0
- package/skills/eng-backend/references/8-arquitetura-servidor.md +49 -0
- package/skills/eng-backend/references/9-testes.md +43 -0
- package/skills/{eng-arch-c4 → eng-backend-arch-c4}/SKILL.md +12 -7
- package/skills/{eng-ms-trace → eng-backend-microservices-trace}/SKILL.md +6 -4
- package/skills/{eng-nestjs → eng-backend-nestjs}/SKILL.md +3 -1
- package/skills/{eng-rabbitmq → eng-backend-rabbitmq}/SKILL.md +3 -1
- package/skills/eng-data-bi/SKILL.md +1 -0
- package/skills/eng-data-debug/SKILL.md +2 -1
- package/skills/eng-data-engineer/SKILL.md +1 -0
- package/skills/eng-data-onboard/SKILL.md +1 -0
- package/skills/eng-data-orchestrator/SKILL.md +1 -0
- package/skills/{eng-performance-engineer → eng-devops-performance-engineer}/SKILL.md +2 -1
- package/skills/eng-frontend/SKILL.md +145 -782
- package/skills/eng-frontend/references/1-fundamentos-programacao.md +37 -0
- package/skills/eng-frontend/references/10-testes.md +47 -0
- package/skills/eng-frontend/references/11-seguranca.md +56 -0
- package/skills/eng-frontend/references/12-qualidade-codigo.md +51 -0
- package/skills/eng-frontend/references/2-html-fundamentals.md +51 -0
- package/skills/eng-frontend/references/3-css-responsive.md +61 -0
- package/skills/eng-frontend/references/4-javascript-dom.md +60 -0
- package/skills/eng-frontend/references/5-state-management.md +55 -0
- package/skills/eng-frontend/references/6-uiux-design.md +59 -0
- package/skills/eng-frontend/references/7-acessibilidade.md +46 -0
- package/skills/eng-frontend/references/8-performance.md +58 -0
- package/skills/eng-frontend/references/9-browser-apis.md +54 -0
- package/skills/{eng-design-system → eng-frontend-design-system}/SKILL.md +4 -2
- package/skills/{eng-microfrontend → eng-frontend-microfrontend}/SKILL.md +4 -2
- package/skills/{eng-browser-extension-builder → eng-global-browser-extension-builder}/SKILL.md +2 -1
- package/skills/{eng-docs-write → eng-global-docs-write}/SKILL.md +8 -4
- package/skills/eng-global-jira-comment/SKILL.md +21 -0
- package/skills/{eng-pr → eng-global-pr}/SKILL.md +7 -3
- package/skills/{eng-task-comment → eng-global-task-comment}/SKILL.md +6 -2
- package/skills/eng-qa-a11y-audit/SKILL.md +1 -0
- package/skills/eng-qa-bug-report/SKILL.md +2 -1
- package/skills/eng-qa-bug-report/TASK_MANAGERS.md +1 -1
- package/skills/eng-qa-cypress-e2e/SKILL.md +1 -0
- package/skills/eng-qa-dev-guide/SKILL.md +1 -0
- package/skills/eng-qa-e2e/SKILL.md +1 -0
- package/skills/eng-qa-e2e-spec-writer/SKILL.md +1 -0
- package/skills/eng-qa-exploratory/SKILL.md +1 -0
- package/skills/eng-qa-gate/SKILL.md +3 -2
- package/skills/eng-qa-graphql-contract/SKILL.md +1 -0
- package/skills/eng-qa-quality-report/SKILL.md +3 -2
- package/skills/eng-qa-test-plan/SKILL.md +1 -0
- package/skills/eng-qa-testsprite/SKILL.md +2 -1
- package/skills/eng-qa-unit-test/SKILL.md +1 -0
- package/skills/eng-scraper/SKILL.md +1 -0
- package/skills/eng-scraper-robot-builder/SKILL.md +1 -0
- package/skills/{eng-cybersecurity → eng-security-cybersecurity}/SKILL.md +3 -2
- package/skills/eng-security-patch/SKILL.md +6 -2
- package/skills/{eng-threat-model → eng-security-threat-model}/SKILL.md +6 -2
- package/skills/eng-security-triage/SKILL.md +4 -0
- package/skills/{context-detect → jarvis-context-detect}/SKILL.md +6 -5
- package/skills/{context-detect → jarvis-context-detect}/assets/context-profile-template.md +3 -3
- package/skills/jarvis-create-specialization/SKILL.md +230 -0
- package/skills/jarvis-create-specialization/assets/referencia-template.md +39 -0
- package/skills/jarvis-create-specialization/assets/skill-especializacao-template.md +78 -0
- package/skills/{docs-central → jarvis-docs-central}/README.md +1 -1
- package/skills/{docs-central → jarvis-docs-central}/SKILL.md +3 -40
- package/skills/{docs-index → jarvis-docs-index}/SKILL.md +8 -4
- package/skills/{init-jarvis → jarvis-init}/SKILL.md +42 -19
- package/skills/{init-jarvis → jarvis-init}/assets/mcp-configs.md +3 -3
- package/skills/{init-jarvis → jarvis-init}/assets/onboarding-checklist.md +1 -1
- package/skills/jarvis-init/assets/passo-adicionar-stacks.md +163 -0
- package/skills/{init-jarvis → jarvis-init}/assets/setup-guide.md +1 -1
- package/skills/jarvis-init/assets/sinais-de-stack.md +54 -0
- package/skills/jarvis-list-specializations/SKILL.md +148 -0
- package/skills/{report-issue → jarvis-report-issue}/SKILL.md +3 -2
- package/skills/{prod-roadmap-report → product-roadmap-report}/SKILL.md +2 -1
- package/skills/product-specs/SKILL.md +45 -0
- package/skills/{prod-specs-update → product-specs-update}/SKILL.md +3 -2
- package/taxonomy.md +3 -21
- package/templates/AGENTS.md +7 -6
- package/templates/CDD aplicado a Prompts.md +5 -5
- package/templates/ENV-template.md +12 -9
- package/templates/engineering/AGENTS-template.md +1 -1
- package/templates/engineering/CONTACTS-template.md +1 -1
- package/templates/product/prod-discovery-template.md +98 -0
- package/{skills/prod-specs/templates → templates/product}/prod-epic-template.md +1 -0
- package/{skills/prod-specs/templates → templates/product}/prod-frd-template.md +1 -0
- package/{skills/prod-specs/templates → templates/product}/prod-issue-template.md +1 -0
- package/{skills/prod-specs/templates → templates/product}/prod-prd-template.md +1 -0
- package/workflows/AGENTS.md +3 -1
- package/workflows/README.md +2 -0
- package/workflows/engineering/eng.bug-audit.md +2 -2
- package/workflows/engineering/eng.build-tech-spec.md +38 -49
- package/workflows/engineering/eng.debug.md +10 -10
- package/workflows/engineering/eng.docs.md +2 -2
- package/workflows/engineering/eng.plan.md +5 -5
- package/workflows/engineering/eng.pr.md +8 -8
- package/workflows/engineering/eng.pre-pr.md +16 -20
- package/workflows/engineering/eng.review.md +5 -10
- package/workflows/engineering/eng.rpa.robot.md +3 -3
- package/workflows/engineering/eng.security-audit.md +5 -5
- package/workflows/engineering/eng.security-incident.md +2 -2
- package/workflows/engineering/eng.security-pipeline.md +2 -2
- package/workflows/engineering/eng.start.md +16 -50
- package/workflows/engineering/eng.work.md +9 -12
- package/workflows/engineering/frontend/eng.frontend-component.md +68 -96
- package/workflows/engineering/frontend/eng.frontend-perf-audit.md +90 -220
- package/workflows/engineering/frontend/eng.frontend-review.md +57 -63
- package/workflows/engineering/qa/eng.qa-quality-report.md +0 -5
- package/workflows/product/prod.roadmap.preview.md +2 -2
- package/workflows/product/prod.spec.breakdown.md +3 -8
- package/workflows/product/prod.spec.discovery.md +152 -0
- package/workflows/product/prod.spec.epic.md +3 -0
- package/workflows/product/prod.spec.frd.md +123 -8
- package/workflows/product/prod.spec.issue.md +3 -2
- package/workflows/product/prod.spec.md +2 -0
- package/workflows/product/prod.spec.prd.md +105 -6
- package/workflows/taxonomy.md +1 -1
- package/workflows/warm-up.md +5 -26
- package/agents/engineering/eng.tech-analyst.agent.md +0 -102
- package/skills/churn-audit/SKILL.md +0 -385
- package/skills/eng-jira-comment/SKILL.md +0 -17
- package/skills/eng-tech-analyst/SKILL.md +0 -529
- package/skills/lovable-prompt-generator/SKILL.md +0 -304
- package/skills/prod-specs/SKILL.md +0 -108
- package/skills/prod-specs/references/prod.spec.clarify.md +0 -176
- package/skills/prod-specs/references/prod.spec.epic.md +0 -107
- package/skills/prod-specs/references/prod.spec.frd.md +0 -135
- package/skills/prod-specs/references/prod.spec.issue.md +0 -145
- package/skills/prod-specs/references/prod.spec.prd.md +0 -118
- package/skills/prod-specs/templates/prod-prd-full-template.md +0 -159
- package/workflows/engineering/ta/eng.ta.atendimento.md +0 -93
- /package/skills/{eng-arch-c4 → eng-backend-arch-c4}/assets/example-code.md +0 -0
- /package/skills/{eng-arch-c4 → eng-backend-arch-c4}/assets/example-component.md +0 -0
- /package/skills/{eng-arch-c4 → eng-backend-arch-c4}/assets/example-container.md +0 -0
- /package/skills/{eng-arch-c4 → eng-backend-arch-c4}/assets/example-context.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/README.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/commands/status.compiled.single.team.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/commands/status.list.projects.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/commands/status.memory.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/commands/status.roadmap.preview.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/references/detailed-guide.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/rules/detailed-guide.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/rules/status-report-rules.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/templates/template-multiple-teams-compiled-status.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/templates/template-projects-list.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/templates/template-single-team-compiled-status.md +0 -0
- /package/skills/{prod-roadmap-report → product-roadmap-report}/templates/template-single-team-status.md +0 -0
- /package/{skills/prod-specs/templates → templates/product}/prod-breakdown-template.md +0 -0
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eng-backend
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Skill base de desenvolvimento backend, válida em qualquer linguagem ou framework: design de APIs,
|
|
5
|
+
autenticação e autorização, banco de dados, concorrência, arquitetura de servidor, testes, segurança,
|
|
6
|
+
performance e tratamento de erros. Pode ser complementada por skills especializados de uma stack.
|
|
7
|
+
Trigger: Use para APIs, autenticação, lógica de negócio, workers, jobs, integrações, caching,
|
|
8
|
+
banco de dados ou backend em geral.
|
|
7
9
|
license: AGPL-3.0
|
|
8
10
|
compatibility: Designed for Claude Code (or similar products)
|
|
9
11
|
allowed-tools: Read Write Edit Glob Grep Bash
|
|
10
12
|
metadata:
|
|
11
13
|
author: jarvis-team
|
|
12
|
-
version: "
|
|
14
|
+
version: "2.0"
|
|
15
|
+
area: backend
|
|
13
16
|
# Campos Claude Code-specific (não fazem parte da spec oficial agentskills.io):
|
|
14
17
|
argument-hint: "[endpoint|auth|worker|integração|refactor|debug] [contexto]"
|
|
15
18
|
disable-model-invocation: false
|
|
16
19
|
---
|
|
17
20
|
|
|
18
|
-
# Eng Backend -
|
|
21
|
+
# Eng Backend - Skill Base de Desenvolvimento de Servidor
|
|
19
22
|
|
|
20
|
-
Você é
|
|
23
|
+
Você é uma **pessoa desenvolvedora backend sênior**, com domínio dos princípios que valem em qualquer linguagem ou framework: APIs, autenticação e autorização, dados, concorrência, arquitetura de servidor, segurança e observabilidade.
|
|
21
24
|
|
|
22
25
|
## Objetivo
|
|
23
26
|
|
|
24
|
-
Construir backends confiáveis, seguros e
|
|
27
|
+
Construir backends confiáveis, seguros e fáceis de evoluir, aplicando a **base universal** de backend. Este skill não presume uma stack: ele descobre a stack do projeto pelo próprio código e segue o padrão que encontra.
|
|
25
28
|
|
|
26
29
|
## Entrada
|
|
27
30
|
|
|
28
|
-
- `$ARGUMENTS` - Operação,
|
|
31
|
+
- `$ARGUMENTS` - Operação, funcionalidade ou problema a resolver (ex: `criar-endpoint-produtos`, `implementar-refresh-de-token`, `worker-envio-email`, `integrar-servico-externo`, `otimizar-consulta-lenta`)
|
|
29
32
|
|
|
30
33
|
## Recursos
|
|
31
34
|
|
|
32
|
-
- **ENV**: `$IDE/ENV.md` (variáveis de ambiente
|
|
33
|
-
- **
|
|
35
|
+
- **ENV**: `$IDE/ENV.md` (variáveis de ambiente e configurações do projeto)
|
|
36
|
+
- **Base universal**: os 13 temas em [references/](references/), carregados sob demanda
|
|
37
|
+
- **Saída**: código e testes no repositório atual
|
|
34
38
|
|
|
35
39
|
---
|
|
36
40
|
|
|
37
41
|
## Pré-requisito
|
|
38
42
|
|
|
39
|
-
Verificar se o `ENV.md` existe e se as variáveis necessárias estão configuradas:
|
|
43
|
+
Verificar se o `ENV.md` existe e se as variáveis necessárias ao projeto estão configuradas:
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
|
-
# Verificar existência do ENV.md
|
|
43
46
|
cat $IDE/ENV.md
|
|
44
|
-
|
|
45
|
-
# Verificar credenciais do message broker (obrigatório para workers RabbitMQ)
|
|
46
|
-
grep "MESSAGE_BROKER" $IDE/ENV.md
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
---
|
|
@@ -51,693 +51,179 @@ grep "MESSAGE_BROKER" $IDE/ENV.md
|
|
|
51
51
|
## Quando Usar
|
|
52
52
|
|
|
53
53
|
Use este skill quando:
|
|
54
|
-
- Criar ou refatorar endpoints
|
|
55
|
-
- Implementar autenticação
|
|
56
|
-
- Criar workers, jobs em background ou processamento assíncrono
|
|
57
|
-
- Integrar com
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
- Escrever testes de backend
|
|
54
|
+
- Criar ou refatorar endpoints e contratos de API
|
|
55
|
+
- Implementar autenticação, autorização e controle de acesso
|
|
56
|
+
- Criar workers, jobs em background ou processamento assíncrono
|
|
57
|
+
- Integrar com serviços externos (webhooks, APIs de terceiros, tentativas de repetição)
|
|
58
|
+
- Modelar dados, escrever consultas ou criar migrações
|
|
59
|
+
- Implementar cache e otimizar desempenho
|
|
60
|
+
- Escrever testes de backend, tratar erros e registrar logs
|
|
61
61
|
|
|
62
62
|
**NÃO usar quando:**
|
|
63
|
-
- A tarefa é exclusivamente de frontend
|
|
64
|
-
- Não há lógica de servidor, API ou processamento assíncrono envolvido
|
|
63
|
+
- A tarefa é exclusivamente de frontend ou de infraestrutura
|
|
64
|
+
- Não há lógica de servidor, API, dados ou processamento assíncrono envolvido
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
68
|
## Validação de Entrada
|
|
69
69
|
|
|
70
|
-
Se
|
|
70
|
+
Se `$ARGUMENTS` está vazio, o skill funciona em modo interativo: pergunte à pessoa o contexto da tarefa de backend antes de prosseguir.
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
74
74
|
## Padrões Críticos
|
|
75
75
|
|
|
76
|
-
### Padrão 1: Ler o
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
# Verificar framework e dependências
|
|
80
|
-
cat package.json | grep -E '"nest|amqplib|@golevelup/nestjs-rabbitmq|redis|prisma|typeorm|drizzle|jest|vitest"'
|
|
76
|
+
### Padrão 1: Ler o projeto antes de escrever
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
ls src/ 2>/dev/null
|
|
78
|
+
Antes de criar qualquer coisa, descubra como o projeto já funciona:
|
|
84
79
|
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
```
|
|
81
|
+
1. Qual é a linguagem e o framework? (pelos arquivos de manifesto e de dependências do projeto)
|
|
82
|
+
2. Como as rotas, os serviços e os dados estão organizados? (estrutura de pastas)
|
|
83
|
+
3. Como a autenticação e a autorização já são feitas?
|
|
84
|
+
4. Como os erros, os logs e os testes são escritos?
|
|
85
|
+
5. Existe uma convenção de nomes e de camadas a seguir?
|
|
87
86
|
```
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
Siga o padrão existente do projeto, **não** o padrão genérico deste skill. Se não conseguir identificar a stack, pergunte antes de prosseguir.
|
|
89
|
+
|
|
90
|
+
### Padrão 2: Segurança por padrão
|
|
90
91
|
|
|
91
92
|
Toda API precisa considerar:
|
|
92
93
|
|
|
93
94
|
```
|
|
94
95
|
1. Validação de entrada → nunca confiar em dados externos
|
|
95
|
-
2. Autenticação → verificar identidade antes de processar
|
|
96
|
-
3. Autorização → verificar permissão
|
|
97
|
-
4.
|
|
98
|
-
5. Sanitização → prevenir injeção (SQL,
|
|
99
|
-
6. Não expor detalhes de erro
|
|
96
|
+
2. Autenticação → verificar a identidade antes de processar
|
|
97
|
+
3. Autorização → verificar a permissão depois de autenticar
|
|
98
|
+
4. Limitação de taxa → proteger contra abuso
|
|
99
|
+
5. Sanitização → prevenir injeção (SQL, comandos, documentos)
|
|
100
|
+
6. Não expor detalhes internos de erro em produção
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
### Padrão 3: Tratamento de
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
// ✅ Erro tipado com contexto
|
|
106
|
-
export class AppError extends Error {
|
|
107
|
-
constructor(
|
|
108
|
-
public readonly message: string,
|
|
109
|
-
public readonly statusCode: number = 500,
|
|
110
|
-
public readonly code: string = 'INTERNAL_ERROR'
|
|
111
|
-
) {
|
|
112
|
-
super(message)
|
|
113
|
-
this.name = 'AppError'
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ✅ Erros de negócio explícitos
|
|
118
|
-
export class NotFoundError extends AppError {
|
|
119
|
-
constructor(resource: string, id: string) {
|
|
120
|
-
super(`${resource} com id '${id}' não encontrado`, 404, 'NOT_FOUND')
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export class UnauthorizedError extends AppError {
|
|
125
|
-
constructor(message = 'Não autorizado') {
|
|
126
|
-
super(message, 401, 'UNAUTHORIZED')
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
103
|
+
### Padrão 3: Tratamento de erros consistente
|
|
130
104
|
|
|
131
|
-
|
|
105
|
+
- Erros tipados, com **código** estável e **mensagem** útil, e com o status de resposta adequado
|
|
106
|
+
- Erros de negócio explícitos (não encontrado, não autorizado, conflito, entrada inválida)
|
|
107
|
+
- O detalhe técnico vai para o log, nunca para a resposta ao cliente
|
|
108
|
+
- O mesmo formato de erro em toda a API
|
|
132
109
|
|
|
133
|
-
|
|
134
|
-
// ✅ Idempotency key para mutations críticas (pagamentos, envios)
|
|
135
|
-
async function processPayment(idempotencyKey: string, data: PaymentData) {
|
|
136
|
-
const existing = await redis.get(`payment:idempotency:${idempotencyKey}`)
|
|
137
|
-
if (existing) return JSON.parse(existing)
|
|
110
|
+
### Padrão 4: Idempotência em operações críticas
|
|
138
111
|
|
|
139
|
-
|
|
140
|
-
await redis.set(`payment:idempotency:${idempotencyKey}`, JSON.stringify(result), 'EX', 86400)
|
|
141
|
-
return result
|
|
142
|
-
}
|
|
143
|
-
```
|
|
112
|
+
Mutações críticas (pagamentos, envios, criação de recursos) devem poder ser repetidas sem repetir o efeito:
|
|
144
113
|
|
|
145
|
-
|
|
114
|
+
- Aceitar uma **chave de idempotência** do chamador
|
|
115
|
+
- Guardar o resultado da primeira execução por um tempo definido
|
|
116
|
+
- Devolver o resultado guardado quando a mesma chave chegar de novo
|
|
146
117
|
|
|
147
|
-
|
|
118
|
+
### Padrão 5: Rastreabilidade entre serviços
|
|
148
119
|
|
|
149
|
-
|
|
150
|
-
Criar/modificar endpoint? → Seção: API Design
|
|
151
|
-
Implementar autenticação? → Seção: Autenticação e Autorização
|
|
152
|
-
Criar worker ou job? → Seção: Workers e Jobs Assíncronos
|
|
153
|
-
Integrar API externa? → Seção: Integrações Externas
|
|
154
|
-
Implementar caching? → Seção: Caching
|
|
155
|
-
Escrever testes? → Seção: Testes
|
|
156
|
-
Debug de problema? → Seção: Debugging e Observabilidade
|
|
157
|
-
```
|
|
120
|
+
Em fluxos que cruzam serviços, propague um **identificador de correlação** nas chamadas de saída e nas mensagens publicadas, e inclua-o nos logs. Sem ele, um defeito intermitente só é rastreável à mão.
|
|
158
121
|
|
|
159
122
|
---
|
|
160
123
|
|
|
161
|
-
##
|
|
162
|
-
|
|
163
|
-
### API Design
|
|
164
|
-
|
|
165
|
-
#### REST — boas práticas
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
// ✅ Estrutura de rotas REST
|
|
169
|
-
GET /products → listar produtos (com paginação)
|
|
170
|
-
GET /products/:id → buscar produto por ID
|
|
171
|
-
POST /products → criar produto
|
|
172
|
-
PUT /products/:id → atualizar produto completo
|
|
173
|
-
PATCH /products/:id → atualizar produto parcialmente
|
|
174
|
-
DELETE /products/:id → remover produto
|
|
175
|
-
|
|
176
|
-
// ✅ Resposta padronizada
|
|
177
|
-
interface ApiResponse<T> {
|
|
178
|
-
data: T
|
|
179
|
-
meta?: {
|
|
180
|
-
page: number
|
|
181
|
-
pageSize: number
|
|
182
|
-
total: number
|
|
183
|
-
totalPages: number
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ✅ Erros padronizados
|
|
188
|
-
interface ApiError {
|
|
189
|
-
error: {
|
|
190
|
-
code: string // ex: "PRODUCT_NOT_FOUND"
|
|
191
|
-
message: string // mensagem legível
|
|
192
|
-
details?: unknown // erros de validação, etc.
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
#### Paginação
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
// ✅ Cursor-based (recomendado para grandes volumes)
|
|
201
|
-
interface CursorPaginationParams {
|
|
202
|
-
cursor?: string // ID do último item retornado
|
|
203
|
-
limit?: number // default: 20, max: 100
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ✅ Offset-based (simples, para volumes menores)
|
|
207
|
-
interface OffsetPaginationParams {
|
|
208
|
-
page?: number // default: 1
|
|
209
|
-
pageSize?: number // default: 20, max: 100
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Implementação com Prisma
|
|
213
|
-
async function listProducts({ page = 1, pageSize = 20 }: OffsetPaginationParams) {
|
|
214
|
-
const [items, total] = await Promise.all([
|
|
215
|
-
prisma.product.findMany({
|
|
216
|
-
skip: (page - 1) * pageSize,
|
|
217
|
-
take: pageSize,
|
|
218
|
-
orderBy: { createdAt: 'desc' },
|
|
219
|
-
}),
|
|
220
|
-
prisma.product.count(),
|
|
221
|
-
])
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
data: items,
|
|
225
|
-
meta: { page, pageSize, total, totalPages: Math.ceil(total / pageSize) },
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
#### Versionamento de API
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
// ✅ Versionamento por URL (mais explícito)
|
|
234
|
-
app.register(v1Routes, { prefix: '/api/v1' })
|
|
235
|
-
app.register(v2Routes, { prefix: '/api/v2' })
|
|
236
|
-
|
|
237
|
-
// ✅ Versionamento por header (para APIs internas)
|
|
238
|
-
// Accept: application/vnd.api+json;version=2
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Autenticação e Autorização
|
|
242
|
-
|
|
243
|
-
#### JWT com refresh token
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
// ✅ Par de tokens: access (curto) + refresh (longo)
|
|
247
|
-
const ACCESS_TOKEN_EXPIRY = '15m'
|
|
248
|
-
const REFRESH_TOKEN_EXPIRY = '7d'
|
|
249
|
-
|
|
250
|
-
async function generateTokens(userId: string) {
|
|
251
|
-
const accessToken = jwt.sign({ sub: userId, type: 'access' }, JWT_SECRET, {
|
|
252
|
-
expiresIn: ACCESS_TOKEN_EXPIRY,
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
const refreshToken = jwt.sign({ sub: userId, type: 'refresh' }, JWT_REFRESH_SECRET, {
|
|
256
|
-
expiresIn: REFRESH_TOKEN_EXPIRY,
|
|
257
|
-
})
|
|
124
|
+
## Base ou especialização?
|
|
258
125
|
|
|
259
|
-
|
|
260
|
-
await db.refreshToken.create({
|
|
261
|
-
data: { token: hashToken(refreshToken), userId, expiresAt: addDays(new Date(), 7) },
|
|
262
|
-
})
|
|
126
|
+
Pergunta-chave para decidir onde um conhecimento mora:
|
|
263
127
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// ✅ Rota de refresh
|
|
268
|
-
async function refreshAccessToken(refreshToken: string) {
|
|
269
|
-
const payload = jwt.verify(refreshToken, JWT_REFRESH_SECRET)
|
|
270
|
-
const stored = await db.refreshToken.findUnique({ where: { token: hashToken(refreshToken) } })
|
|
271
|
-
|
|
272
|
-
if (!stored || stored.revokedAt || stored.expiresAt < new Date()) {
|
|
273
|
-
throw new UnauthorizedError('Refresh token inválido ou expirado')
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return generateTokens(payload.sub)
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
#### RBAC — Role-Based Access Control
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
// ✅ Definição de roles e permissões
|
|
284
|
-
const permissions = {
|
|
285
|
-
admin: ['products:read', 'products:write', 'products:delete', 'users:manage'],
|
|
286
|
-
editor: ['products:read', 'products:write'],
|
|
287
|
-
viewer: ['products:read'],
|
|
288
|
-
} as const
|
|
289
|
-
|
|
290
|
-
type Permission = (typeof permissions)[keyof typeof permissions][number]
|
|
291
|
-
|
|
292
|
-
// ✅ Middleware de autorização
|
|
293
|
-
function requirePermission(permission: Permission) {
|
|
294
|
-
return async (req: Request, res: Response, next: NextFunction) => {
|
|
295
|
-
const userPermissions = permissions[req.user.role] ?? []
|
|
296
|
-
if (!userPermissions.includes(permission)) {
|
|
297
|
-
throw new ForbiddenError(`Permissão '${permission}' necessária`)
|
|
298
|
-
}
|
|
299
|
-
next()
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Uso na rota
|
|
304
|
-
router.delete('/products/:id', authenticate, requirePermission('products:delete'), deleteProduct)
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
#### OAuth2 — fluxo básico
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
// ✅ Authorization Code Flow (para apps com frontend)
|
|
311
|
-
// 1. Redirecionar para provider → GET /oauth/authorize?provider=github
|
|
312
|
-
// 2. Receber callback com code → GET /oauth/callback?code=xxx
|
|
313
|
-
// 3. Trocar code por token → POST ao provider
|
|
314
|
-
// 4. Buscar perfil do usuário → GET /user no provider
|
|
315
|
-
// 5. Criar/atualizar usuário local → gerar tokens da aplicação
|
|
316
|
-
|
|
317
|
-
async function handleOAuthCallback(provider: string, code: string) {
|
|
318
|
-
const { access_token } = await exchangeCodeForToken(provider, code)
|
|
319
|
-
const profile = await fetchUserProfile(provider, access_token)
|
|
320
|
-
|
|
321
|
-
const user = await upsertUser({
|
|
322
|
-
email: profile.email,
|
|
323
|
-
name: profile.name,
|
|
324
|
-
oauthProvider: provider,
|
|
325
|
-
oauthId: profile.id,
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
return generateTokens(user.id)
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### Workers e Jobs Assíncronos
|
|
333
|
-
|
|
334
|
-
#### RabbitMQ com NestJS — padrão do projeto
|
|
335
|
-
|
|
336
|
-
O projeto usa RabbitMQ como broker de mensagens. Verificar variáveis no ENV.md:
|
|
337
|
-
|
|
338
|
-
```bash
|
|
339
|
-
grep "MESSAGE_BROKER\|RABBITMQ" $IDE/ENV.md
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
**Variáveis esperadas:** `MESSAGE_BROKER_URL`, `MESSAGE_BROKER_USER`, `MESSAGE_BROKER_PASS` (ou equivalentes — checar ENV.md).
|
|
343
|
-
|
|
344
|
-
##### Publicar mensagem (Producer)
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
// ✅ NestJS com @golevelup/nestjs-rabbitmq
|
|
348
|
-
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'
|
|
349
|
-
import { Injectable } from '@nestjs/common'
|
|
350
|
-
|
|
351
|
-
@Injectable()
|
|
352
|
-
export class EmailProducerService {
|
|
353
|
-
constructor(private readonly amqpConnection: AmqpConnection) {}
|
|
354
|
-
|
|
355
|
-
async sendWelcomeEmail(to: string, name: string): Promise<void> {
|
|
356
|
-
await this.amqpConnection.publish(
|
|
357
|
-
'email.exchange', // exchange
|
|
358
|
-
'email.welcome', // routing key
|
|
359
|
-
{ to, name }, // payload (serializado como JSON)
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
##### Consumir mensagem (Consumer / Worker)
|
|
366
|
-
|
|
367
|
-
```typescript
|
|
368
|
-
// ✅ Consumer com @golevelup/nestjs-rabbitmq
|
|
369
|
-
import { RabbitSubscribe, Nack } from '@golevelup/nestjs-rabbitmq'
|
|
370
|
-
import { Injectable, Logger } from '@nestjs/common'
|
|
371
|
-
|
|
372
|
-
@Injectable()
|
|
373
|
-
export class EmailConsumerService {
|
|
374
|
-
private readonly logger = new Logger(EmailConsumerService.name)
|
|
375
|
-
|
|
376
|
-
@RabbitSubscribe({
|
|
377
|
-
exchange: 'email.exchange',
|
|
378
|
-
routingKey: 'email.welcome',
|
|
379
|
-
queue: 'email.welcome.queue',
|
|
380
|
-
queueOptions: {
|
|
381
|
-
durable: true,
|
|
382
|
-
deadLetterExchange: 'email.exchange.dlx',
|
|
383
|
-
},
|
|
384
|
-
})
|
|
385
|
-
async handleWelcomeEmail(payload: { to: string; name: string }): Promise<void | Nack> {
|
|
386
|
-
try {
|
|
387
|
-
await sendEmail({ to: payload.to, template: 'welcome', data: { name: payload.name } })
|
|
388
|
-
} catch (error) {
|
|
389
|
-
this.logger.error({ error, payload }, 'Falha ao processar email de boas-vindas')
|
|
390
|
-
return new Nack(false) // rejeitar sem requeue → vai para DLX
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
##### Configuração do módulo
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
// ✅ RabbitMQModule no AppModule
|
|
400
|
-
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'
|
|
401
|
-
|
|
402
|
-
RabbitMQModule.forRootAsync({
|
|
403
|
-
useFactory: (configService: ConfigService) => ({
|
|
404
|
-
uri: configService.getOrThrow('MESSAGE_BROKER_URL'),
|
|
405
|
-
exchanges: [
|
|
406
|
-
{ name: 'email.exchange', type: 'direct', options: { durable: true } },
|
|
407
|
-
{ name: 'email.exchange.dlx', type: 'direct', options: { durable: true } },
|
|
408
|
-
],
|
|
409
|
-
connectionInitOptions: { wait: true },
|
|
410
|
-
}),
|
|
411
|
-
inject: [ConfigService],
|
|
412
|
-
})
|
|
413
|
-
```
|
|
128
|
+
> **Esse conhecimento continua válido quando eu mudo de linguagem ou de framework?**
|
|
129
|
+
> **Sim** → pertence à base universal (este skill). **Não** → pertence a uma especialização de stack.
|
|
414
130
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
// ✅ @nestjs/schedule — decorator nativo
|
|
419
|
-
import { Injectable, Logger } from '@nestjs/common'
|
|
420
|
-
import { Cron, CronExpression } from '@nestjs/schedule'
|
|
421
|
-
|
|
422
|
-
@Injectable()
|
|
423
|
-
export class ReportSchedulerService {
|
|
424
|
-
private readonly logger = new Logger(ReportSchedulerService.name)
|
|
425
|
-
|
|
426
|
-
// Rodar às 9h todo dia útil (horário de São Paulo)
|
|
427
|
-
@Cron('0 9 * * 1-5', { timeZone: 'America/Sao_Paulo' })
|
|
428
|
-
async handleDailyReport(): Promise<void> {
|
|
429
|
-
this.logger.log('Iniciando relatório diário')
|
|
430
|
-
try {
|
|
431
|
-
await this.reportService.generateDaily()
|
|
432
|
-
} catch (error) {
|
|
433
|
-
this.logger.error({ error }, 'Falha ao gerar relatório diário')
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### Integrações Externas
|
|
440
|
-
|
|
441
|
-
#### Padrão de integração robusta
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
// ✅ Cliente HTTP com retry e timeout
|
|
445
|
-
import axios, { AxiosInstance } from 'axios'
|
|
446
|
-
import axiosRetry from 'axios-retry'
|
|
447
|
-
|
|
448
|
-
function createHttpClient(baseURL: string): AxiosInstance {
|
|
449
|
-
const client = axios.create({
|
|
450
|
-
baseURL,
|
|
451
|
-
timeout: 10_000, // 10 segundos
|
|
452
|
-
headers: { 'Content-Type': 'application/json' },
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
// Retry automático para erros de rede e 5xx
|
|
456
|
-
axiosRetry(client, {
|
|
457
|
-
retries: 3,
|
|
458
|
-
retryDelay: axiosRetry.exponentialDelay,
|
|
459
|
-
retryCondition: (error) =>
|
|
460
|
-
axiosRetry.isNetworkError(error) ||
|
|
461
|
-
axiosRetry.isRetryableError(error),
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
return client
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
#### Webhooks — receber e processar
|
|
469
|
-
|
|
470
|
-
```typescript
|
|
471
|
-
// ✅ Verificação de assinatura (ex: Stripe)
|
|
472
|
-
function verifyWebhookSignature(payload: Buffer, signature: string, secret: string): boolean {
|
|
473
|
-
const expected = crypto
|
|
474
|
-
.createHmac('sha256', secret)
|
|
475
|
-
.update(payload)
|
|
476
|
-
.digest('hex')
|
|
477
|
-
|
|
478
|
-
// Comparação segura contra timing attacks
|
|
479
|
-
return crypto.timingSafeEqual(
|
|
480
|
-
Buffer.from(signature),
|
|
481
|
-
Buffer.from(expected)
|
|
482
|
-
)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// ✅ Controller NestJS para webhook com processamento assíncrono
|
|
486
|
-
@Controller('webhooks')
|
|
487
|
-
export class WebhookController {
|
|
488
|
-
constructor(private readonly stripeProducer: StripeEventProducerService) {}
|
|
489
|
-
|
|
490
|
-
@Post('stripe')
|
|
491
|
-
@HttpCode(200)
|
|
492
|
-
async handleStripe(
|
|
493
|
-
@Headers('stripe-signature') signature: string,
|
|
494
|
-
@Req() req: RawBodyRequest<Request>,
|
|
495
|
-
) {
|
|
496
|
-
if (!verifyWebhookSignature(req.rawBody!, signature, process.env.STRIPE_WEBHOOK_SECRET!)) {
|
|
497
|
-
throw new BadRequestException('Assinatura inválida')
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const event = JSON.parse(req.rawBody!.toString())
|
|
501
|
-
|
|
502
|
-
// Enfileirar no RabbitMQ — processar de forma assíncrona
|
|
503
|
-
await this.stripeProducer.publish('stripe.events', event.type, event)
|
|
504
|
-
|
|
505
|
-
return { received: true }
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
### Caching
|
|
511
|
-
|
|
512
|
-
#### Estratégias de cache com Redis
|
|
513
|
-
|
|
514
|
-
```typescript
|
|
515
|
-
// ✅ Cache-aside (padrão mais comum)
|
|
516
|
-
async function getProduct(id: string): Promise<Product> {
|
|
517
|
-
const cacheKey = `product:${id}`
|
|
518
|
-
|
|
519
|
-
// 1. Verificar cache
|
|
520
|
-
const cached = await redis.get(cacheKey)
|
|
521
|
-
if (cached) return JSON.parse(cached)
|
|
522
|
-
|
|
523
|
-
// 2. Buscar no banco
|
|
524
|
-
const product = await db.product.findUniqueOrThrow({ where: { id } })
|
|
525
|
-
|
|
526
|
-
// 3. Armazenar no cache
|
|
527
|
-
await redis.set(cacheKey, JSON.stringify(product), 'EX', 300) // TTL: 5 minutos
|
|
528
|
-
|
|
529
|
-
return product
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// ✅ Invalidar cache ao atualizar
|
|
533
|
-
async function updateProduct(id: string, data: UpdateProductDto): Promise<Product> {
|
|
534
|
-
const product = await db.product.update({ where: { id }, data })
|
|
535
|
-
|
|
536
|
-
// Invalidar cache deste produto e listas relacionadas
|
|
537
|
-
await redis.del(`product:${id}`)
|
|
538
|
-
await redis.del('products:list:*') // ou usar tags
|
|
539
|
-
|
|
540
|
-
return product
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
#### Quando usar cada estratégia
|
|
545
|
-
|
|
546
|
-
| Estratégia | Quando usar |
|
|
547
|
-
|-----------|-------------|
|
|
548
|
-
| Cache-aside (lazy) | Dados lidos frequentemente, escritas ocasionais |
|
|
549
|
-
| Write-through | Dados críticos onde consistência é prioridade |
|
|
550
|
-
| Write-behind | Alta frequência de escrita, consistência eventual ok |
|
|
551
|
-
| TTL curto (< 1min) | Dados mutáveis com tolerância a leve stale |
|
|
552
|
-
| TTL longo (> 1h) | Dados estáticos ou de referência |
|
|
553
|
-
| Cache de sessão | User session, tokens temporários |
|
|
554
|
-
|
|
555
|
-
### Testes
|
|
556
|
-
|
|
557
|
-
#### Testes unitários
|
|
558
|
-
|
|
559
|
-
```typescript
|
|
560
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
561
|
-
import { ProductService } from './product.service'
|
|
562
|
-
import { ProductRepository } from './product.repository'
|
|
563
|
-
|
|
564
|
-
describe('ProductService', () => {
|
|
565
|
-
let service: ProductService
|
|
566
|
-
let repository: ProductRepository
|
|
567
|
-
|
|
568
|
-
beforeEach(() => {
|
|
569
|
-
// ✅ Mock do repositório — não precisa de banco real
|
|
570
|
-
repository = {
|
|
571
|
-
findById: vi.fn(),
|
|
572
|
-
create: vi.fn(),
|
|
573
|
-
update: vi.fn(),
|
|
574
|
-
} as unknown as ProductRepository
|
|
575
|
-
|
|
576
|
-
service = new ProductService(repository)
|
|
577
|
-
})
|
|
578
|
-
|
|
579
|
-
it('lança NotFoundError quando produto não existe', async () => {
|
|
580
|
-
vi.mocked(repository.findById).mockResolvedValue(null)
|
|
581
|
-
|
|
582
|
-
await expect(service.getById('id-inexistente')).rejects.toThrow('Produto não encontrado')
|
|
583
|
-
})
|
|
131
|
+
---
|
|
584
132
|
|
|
585
|
-
|
|
586
|
-
const product = { id: '1', name: 'Produto A', price: 100 }
|
|
587
|
-
vi.mocked(repository.findById).mockResolvedValue(product)
|
|
133
|
+
## Árvore de Decisão
|
|
588
134
|
|
|
589
|
-
const result = await service.getById('1')
|
|
590
|
-
expect(result).toEqual(product)
|
|
591
|
-
})
|
|
592
|
-
})
|
|
593
135
|
```
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
describe('POST /api/products', () => {
|
|
604
|
-
let app: INestApplication
|
|
605
|
-
|
|
606
|
-
beforeAll(async () => {
|
|
607
|
-
const module: TestingModule = await Test.createTestingModule({
|
|
608
|
-
imports: [ProductsModule],
|
|
609
|
-
})
|
|
610
|
-
.overrideProvider(ProductRepository)
|
|
611
|
-
.useValue({ create: jest.fn(), findById: jest.fn() })
|
|
612
|
-
.compile()
|
|
613
|
-
|
|
614
|
-
app = module.createNestApplication()
|
|
615
|
-
app.useGlobalPipes(new ValidationPipe({ whitelist: true }))
|
|
616
|
-
await app.init()
|
|
617
|
-
})
|
|
618
|
-
|
|
619
|
-
afterAll(() => app.close())
|
|
620
|
-
|
|
621
|
-
it('cria produto e retorna 201', async () => {
|
|
622
|
-
const response = await request(app.getHttpServer())
|
|
623
|
-
.post('/api/products')
|
|
624
|
-
.set('Authorization', `Bearer ${testToken}`)
|
|
625
|
-
.send({ name: 'Produto Teste', price: 29.90 })
|
|
626
|
-
|
|
627
|
-
expect(response.status).toBe(201)
|
|
628
|
-
expect(response.body).toMatchObject({
|
|
629
|
-
data: { name: 'Produto Teste', price: 29.90 },
|
|
630
|
-
})
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
it('retorna 400 quando dados inválidos', async () => {
|
|
634
|
-
const response = await request(app.getHttpServer())
|
|
635
|
-
.post('/api/products')
|
|
636
|
-
.set('Authorization', `Bearer ${testToken}`)
|
|
637
|
-
.send({ name: '' }) // nome vazio, preço ausente
|
|
638
|
-
|
|
639
|
-
expect(response.status).toBe(400)
|
|
640
|
-
expect(response.body.message).toBeDefined()
|
|
641
|
-
})
|
|
642
|
-
})
|
|
136
|
+
Criar ou modificar um endpoint? → tema 6 (API e comunicação) e tema 10 (segurança)
|
|
137
|
+
Implementar autenticação? → tema 6 (API) e tema 10 (segurança)
|
|
138
|
+
Modelar dados, consultas ou migração? → tema 5 (banco de dados)
|
|
139
|
+
Criar worker, job ou fila? → tema 7 (concorrência) e tema 8 (servidor)
|
|
140
|
+
Integrar um serviço externo? → tema 6 (API), tema 8 (resiliência) e tema 12 (erros)
|
|
141
|
+
Implementar cache ou otimizar? → tema 11 (performance)
|
|
142
|
+
Escrever testes? → tema 9 (testes)
|
|
143
|
+
Estruturar módulos e camadas? → tema 2 (design) e tema 3 (patterns)
|
|
144
|
+
Depurar um problema? → tema 12 (erros e logging)
|
|
643
145
|
```
|
|
644
146
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
#### Logs estruturados (pino)
|
|
648
|
-
|
|
649
|
-
```typescript
|
|
650
|
-
import pino from 'pino'
|
|
651
|
-
|
|
652
|
-
export const logger = pino({
|
|
653
|
-
level: process.env.LOG_LEVEL ?? 'info',
|
|
654
|
-
...(process.env.NODE_ENV !== 'production' && {
|
|
655
|
-
transport: { target: 'pino-pretty' },
|
|
656
|
-
}),
|
|
657
|
-
})
|
|
147
|
+
---
|
|
658
148
|
|
|
659
|
-
|
|
660
|
-
logger.info({ userId, productId, action: 'product.updated' }, 'Produto atualizado')
|
|
661
|
-
logger.error({ error, userId, requestId }, 'Falha ao processar pagamento')
|
|
149
|
+
## Fluxo de Trabalho
|
|
662
150
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
151
|
+
1. **Entender**: ler o projeto (Padrão 1) e esclarecer o requisito, inclusive o que **não** está no escopo
|
|
152
|
+
2. **Projetar**: escolher a abordagem na base universal; decidir contratos, erros e segurança antes de codar
|
|
153
|
+
3. **Implementar**: seguir o padrão do projeto, em passos pequenos
|
|
154
|
+
4. **Testar**: cobrir o caso feliz, os casos de erro e os limites
|
|
155
|
+
5. **Validar**: percorrer o checklist de conclusão abaixo
|
|
666
156
|
|
|
667
|
-
|
|
157
|
+
---
|
|
668
158
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
159
|
+
## Base Universal
|
|
160
|
+
|
|
161
|
+
Carregue **só o tema necessário** para a tarefa, em vez de todos de uma vez.
|
|
162
|
+
|
|
163
|
+
| # | Tema | Nível | Carregue quando |
|
|
164
|
+
|---|------|-------|-----------------|
|
|
165
|
+
| 1 | [Fundamentos de programação](references/1-fundamentos-programacao.md) | Fundamental | Precisar revisar conceitos básicos de linguagem |
|
|
166
|
+
| 2 | [Design e arquitetura](references/2-design-arquitetura.md) | Fundamental | Estruturar módulos, camadas ou aplicar SOLID e arquiteturas |
|
|
167
|
+
| 3 | [Design patterns](references/3-design-patterns.md) | Importante | Escolher um padrão para um problema recorrente |
|
|
168
|
+
| 4 | [Algoritmos e estruturas](references/4-algoritmos-estruturas.md) | Fundamental | Analisar complexidade ou escolher uma estrutura de dados |
|
|
169
|
+
| 5 | [Banco de dados](references/5-banco-dados.md) | Fundamental | Modelar dados, consultas, índices, transações e migrações |
|
|
170
|
+
| 6 | [API e comunicação](references/6-api-comunicacao.md) | Fundamental | Projetar endpoints, autenticação, paginação e versionamento |
|
|
171
|
+
| 7 | [Concorrência e assincronismo](references/7-concorrencia-async.md) | Importante | Lidar com condições de corrida, jobs e processamento assíncrono |
|
|
172
|
+
| 8 | [Arquitetura de servidor](references/8-arquitetura-servidor.md) | Importante | Escalar, tornar resiliente ou distribuir o sistema |
|
|
173
|
+
| 9 | [Testes](references/9-testes.md) | Fundamental | Definir a estratégia e a qualidade dos testes |
|
|
174
|
+
| 10 | [Segurança](references/10-seguranca.md) | Fundamental | Revisar vulnerabilidades, autenticação e dados sensíveis |
|
|
175
|
+
| 11 | [Performance](references/11-performance.md) | Importante | Medir e otimizar desempenho e cache |
|
|
176
|
+
| 12 | [Erros e logging](references/12-erros-logging.md) | Importante | Tratar falhas, registrar logs e observar o sistema |
|
|
177
|
+
| 13 | [Qualidade de código](references/13-qualidade-codigo.md) | Fundamental | Refatorar, revisar código e documentar |
|
|
677
178
|
|
|
678
179
|
---
|
|
679
180
|
|
|
680
181
|
## Regras
|
|
681
182
|
|
|
682
183
|
### Nunca
|
|
683
|
-
- Expor
|
|
684
|
-
- Confiar em dados de entrada sem validação (
|
|
685
|
-
- Armazenar senhas em texto plano
|
|
686
|
-
- Commitar
|
|
687
|
-
- Fazer operações
|
|
688
|
-
- Processar webhooks de forma síncrona (enfileirar e responder
|
|
184
|
+
- Expor detalhes internos de erro em respostas de produção
|
|
185
|
+
- Confiar em dados de entrada sem validação (parâmetros, corpo e cabeçalhos)
|
|
186
|
+
- Armazenar senhas em texto plano
|
|
187
|
+
- Commitar segredos, chaves de API ou credenciais no código
|
|
188
|
+
- Fazer operações bloqueantes onde o modelo de execução não permite
|
|
189
|
+
- Processar webhooks de forma síncrona (enfileirar e responder rapidamente)
|
|
689
190
|
|
|
690
191
|
### Sempre
|
|
691
|
-
- Validar e sanitizar toda entrada externa
|
|
192
|
+
- Validar e sanitizar toda entrada externa
|
|
692
193
|
- Usar variáveis de ambiente para configuração
|
|
693
|
-
- Incluir tratamento de erros e
|
|
694
|
-
- Testar casos de erro e
|
|
695
|
-
-
|
|
194
|
+
- Incluir tratamento de erros e alternativas em integrações externas
|
|
195
|
+
- Testar os casos de erro e os limites, não só o caso feliz
|
|
196
|
+
- Registrar logs com contexto suficiente para o diagnóstico
|
|
696
197
|
- Ler o código existente antes de criar novas abstrações
|
|
697
198
|
|
|
698
199
|
---
|
|
699
200
|
|
|
700
201
|
## Tratamento de Erros
|
|
701
202
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// ✅ Erros de negócio explícitos
|
|
716
|
-
export class NotFoundError extends AppError {
|
|
717
|
-
constructor(resource: string, id: string) {
|
|
718
|
-
super(`${resource} com id '${id}' não encontrado`, 404, 'NOT_FOUND')
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
export class UnauthorizedError extends AppError {
|
|
723
|
-
constructor(message = 'Não autorizado') {
|
|
724
|
-
super(message, 401, 'UNAUTHORIZED')
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
```
|
|
203
|
+
### Stack ou dependência não identificada
|
|
204
|
+
- Procurar o manifesto de dependências e a estrutura do projeto
|
|
205
|
+
- Se não encontrar, perguntar à pessoa antes de prosseguir
|
|
206
|
+
|
|
207
|
+
### Código existente com padrão diferente
|
|
208
|
+
- Seguir o padrão do projeto, não o genérico deste skill
|
|
209
|
+
- Registrar a divergência quando relevante
|
|
210
|
+
|
|
211
|
+
### Teste falha depois de uma mudança
|
|
212
|
+
- Verificar se o teste já falhava antes
|
|
213
|
+
- Isolar a causa antes de ajustar o código ou o teste
|
|
728
214
|
|
|
729
215
|
---
|
|
730
216
|
|
|
731
217
|
## Checklist de Conclusão
|
|
732
218
|
|
|
733
|
-
- [ ] Entrada validada
|
|
219
|
+
- [ ] Entrada validada
|
|
734
220
|
- [ ] Autenticação e autorização verificadas
|
|
735
|
-
- [ ] Erros tipados e tratados
|
|
221
|
+
- [ ] Erros tipados e tratados, sem vazar detalhes em produção
|
|
736
222
|
- [ ] Logs estruturados com contexto adequado
|
|
737
|
-
- [ ] Testes
|
|
738
|
-
- [ ]
|
|
739
|
-
- [ ] Cache
|
|
740
|
-
- [ ] Operações destrutivas com confirmação
|
|
223
|
+
- [ ] Testes cobrindo o caso feliz, os erros e os limites
|
|
224
|
+
- [ ] Limitação de taxa considerada (se o endpoint for público)
|
|
225
|
+
- [ ] Cache aplicado onde faz sentido
|
|
226
|
+
- [ ] Operações destrutivas com confirmação ou idempotência
|
|
741
227
|
|
|
742
228
|
---
|
|
743
229
|
|
|
@@ -746,9 +232,9 @@ export class UnauthorizedError extends AppError {
|
|
|
746
232
|
| Artefato | Descrição |
|
|
747
233
|
|----------|-----------|
|
|
748
234
|
| Endpoint(s) | Rota com validação, autenticação e tratamento de erro |
|
|
749
|
-
|
|
|
750
|
-
| Worker
|
|
751
|
-
| Testes |
|
|
235
|
+
| Serviço ou caso de uso | Lógica de negócio isolada e testável |
|
|
236
|
+
| Worker ou job | Processamento assíncrono com repetição e observabilidade |
|
|
237
|
+
| Testes | Cobrindo o caso feliz e os erros |
|
|
752
238
|
|
|
753
239
|
---
|
|
754
240
|
|
|
@@ -757,20 +243,32 @@ export class UnauthorizedError extends AppError {
|
|
|
757
243
|
```
|
|
758
244
|
Implementação backend concluída!
|
|
759
245
|
|
|
760
|
-
Framework: NestJS
|
|
761
|
-
ORM: {Prisma / TypeORM / Drizzle}
|
|
762
246
|
Funcionalidade: {descrição do que foi implementado}
|
|
247
|
+
Stack do projeto: {linguagem e framework identificados no código}
|
|
763
248
|
|
|
764
249
|
Segurança: {validação de entrada / autenticação / autorização}
|
|
765
250
|
Testes: {criados / pendentes}
|
|
766
251
|
Cache: {implementado / não necessário}
|
|
767
252
|
|
|
768
|
-
Próximo passo: {rodar testes /
|
|
253
|
+
Próximo passo: {rodar os testes / integrar / publicar}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Aviso: uso só da skill base
|
|
259
|
+
|
|
260
|
+
Quando o trabalho usar **só a skill base** e envolver uma tecnologia, framework ou ferramenta para a qual **não há skill especializado** disponível, avise a pessoa de forma explícita:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
ℹ️ Estou usando só a skill base de backend.
|
|
264
|
+
Não há skill especializado para {tecnologia}; vou seguir os princípios universais
|
|
265
|
+
e o padrão que encontrei no projeto.
|
|
769
266
|
```
|
|
770
267
|
|
|
268
|
+
Não invente convenções nem comandos específicos da tecnologia: siga o código do projeto e a base universal.
|
|
269
|
+
|
|
771
270
|
---
|
|
772
271
|
|
|
773
272
|
## Recursos Adicionais
|
|
774
273
|
|
|
775
|
-
- **
|
|
776
|
-
- **Referências**: Veja [references/](references/) para links de documentação local
|
|
274
|
+
- **Base universal**: os 13 temas em [references/](references/)
|