diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 9c8b891..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,656 +0,0 @@ - -# 📋 AGENTS.md - Diretrizes Essenciais para Modernização do SDK NFE.io - -> **Meta**: Modernizar completamente o SDK NFE.io de JavaScript/callbacks para TypeScript moderno com geração automática a partir de OpenAPI, mantendo compatibilidade funcional. - ---- - -## 🎯 Contexto do Projeto - -### Estado Atual (v2.0.0) -- **Tecnologia**: JavaScript ES5/ES6, callbacks + promises via biblioteca `when` -- **Node.js**: >= v12.0.0 -- **Estrutura**: Manual baseada em `BaseResource.extend()` pattern -- **Dependências**: `when@3.1.0` (desatualizado) -- **API**: REST API v1 - `api.nfe.io/v1/` -- **Recursos disponíveis**: - - Companies (CRUD + upload certificate) - - ServiceInvoices (create, list, retrieve, cancel, sendemail, downloadPdf, downloadXml) - - LegalPeople (CRUD - scoped por company_id) - - NaturalPeople (CRUD - scoped por company_id) - - Webhooks (CRUD) - -### Estado Desejado (v3.0.0) -- **Tecnologia**: TypeScript 5.3+, async/await nativo, Fetch API -- **Node.js**: >= 18.0.0 (suporte nativo a Fetch) -- **Estrutura**: Código auto-gerado do OpenAPI + camada DX handwritten -- **Dependências**: Zero runtime dependencies (apenas devDependencies) -- **Qualidade**: Testes completos, CI/CD, documentação auto-gerada - ---- - -## 🚨 REGRAS CRÍTICAS - LEIA PRIMEIRO - -### ❌ NUNCA FAÇA ISSO: -1. **Nunca edite código em `src/generated/`** - É auto-gerado e será sobrescrito -2. **Nunca remova backward compatibility** sem documentar no CHANGELOG -3. **Nunca commite sem rodar**: `npm run typecheck && npm run lint && npm test` -4. **Nunca publique sem atualizar**: CHANGELOG.md e package.json version -5. **Nunca use `any` no TypeScript** - Use tipos explícitos ou `unknown` - -### ✅ SEMPRE FAÇA ISSO: -1. **Sempre documente métodos públicos** com JSDoc completo -2. **Sempre escreva testes** junto com o código novo -3. **Sempre valide o OpenAPI spec** antes de gerar código -4. **Sempre use tipos do generated/** nos resources handwritten -5. **Sempre teste contra sandbox** antes de release - ---- - -## 📁 Estrutura de Arquivos Obrigatória - -``` -client-nodejs/ # nfe-io - Core SDK -├── openapi/ -│ ├── spec/ -│ │ └── nfe-api.json # ⚠️ SOURCE OF TRUTH - OpenAPI spec -│ └── generator-config.yaml # Configuração do gerador -│ -├── src/ -│ ├── core/ # ✏️ Core SDK implementation -│ │ ├── client.ts # NfeClient principal -│ │ ├── types.ts # TypeScript types completos -│ │ ├── errors/ # Sistema de erros -│ │ │ └── index.ts -│ │ ├── http/ # HTTP client layer -│ │ │ └── client.ts -│ │ └── resources/ # API Resources -│ │ ├── companies.ts -│ │ ├── service-invoices.ts -│ │ ├── legal-people.ts -│ │ ├── natural-people.ts -│ │ └── webhooks.ts -│ │ -│ └── index.ts # Public API exports -│ -├── scripts/ -│ ├── download-openapi.ts # Download spec da API -│ └── validate-spec.ts # Valida OpenAPI spec -│ -├── tests/ -│ ├── unit/ # Testes unitários -│ ├── integration/ # Testes de integração -│ └── setup.ts # Test setup -│ -├── examples/ # Exemplos de uso -│ ├── basic-usage-esm.js -│ └── basic-usage-cjs.cjs -│ -├── docs/ # Documentação -├── .github/workflows/ # CI/CD pipelines -│ -├── package.json -├── tsconfig.json -├── tsup.config.ts -├── vitest.config.ts -├── CONTRIBUTING.md # Guidelines para extensões -└── README.md - -NOTA: Adaptadores MCP e n8n foram movidos para repositórios separados: - - @nfe-io/mcp-server (https://github.com/nfe/mcp-server) - - @nfe-io/n8n-nodes (https://github.com/nfe/n8n-nodes) -``` - ---- - -## 🔄 Fluxo de Trabalho Obrigatório - -### 1️⃣ Inicialização (Faça UMA VEZ) -```bash -# Criar estrutura base -mkdir -p nfe-io-sdk-v3/{openapi/spec,src/{generated,client,runtime,errors,utils},scripts,tests,examples} -cd nfe-io-sdk-v3 - -# Inicializar projeto -npm init -y - -# Instalar dependências essenciais -npm install --save-dev \ - typescript@^5.3.0 \ - tsup@^8.0.0 \ - tsx@^4.7.0 \ - vitest@^1.0.0 \ - @vitest/coverage-v8 \ - eslint@^8.56.0 \ - prettier@^3.2.0 \ - openapi-typescript@^6.7.0 - -npm install zod@^3.22.0 - -# Criar configurações base -cat > tsconfig.json << 'EOF' -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "bundler", - "declaration": true, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - } -} -EOF - -cat > package.json << 'EOF' -{ - "name": "nfe-io", - "version": "3.0.0-beta.1", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "scripts": { - "download-spec": "tsx scripts/download-openapi.ts", - "validate-spec": "tsx scripts/validate-spec.ts", - "generate": "tsx scripts/generate-sdk.ts", - "build": "npm run generate && tsup", - "test": "vitest", - "lint": "eslint src --ext .ts", - "typecheck": "tsc --noEmit" - } -} -EOF -``` - -### 2️⃣ A Cada Nova Feature (Ciclo Repetível) -```bash -# 1. Atualizar OpenAPI spec -npm run download-spec -npm run validate-spec - -# 2. Gerar código -npm run generate - -# 3. Implementar código handwritten -# Edite arquivos em src/client/, src/runtime/, etc. - -# 4. Escrever testes -# Crie testes em tests/unit/ ou tests/integration/ - -# 5. Validar qualidade -npm run typecheck # DEVE passar -npm run lint # DEVE passar -npm test # DEVE passar -npm run build # DEVE gerar dist/ - -# 6. Commit -git add . -git commit -m "feat: implementa X" -``` - -### 3️⃣ Antes de Cada Commit -```bash -# Checklist obrigatório -✅ npm run typecheck # Zero erros -✅ npm run lint # Zero warnings -✅ npm test # 100% passing -✅ npm run build # Build sucesso -✅ git diff # Revisar mudanças -✅ CHANGELOG.md # Atualizado se necessário -``` - ---- - -## 🎯 Prioridades de Implementação - -### 🔴 CRÍTICO - Implementar PRIMEIRO (Dias 1-5) - -#### Sprint 1: Fundação -**Objetivo**: Projeto TypeScript funcional + geração de código básica - -**Tarefas**: -1. ✅ Inicializar projeto TypeScript moderno - - Setup package.json, tsconfig.json, tsup.config.ts - - Configurar ESLint + Prettier - - Estrutura de diretórios - -2. ✅ Obter OpenAPI Spec - - **IMPORTANTE**: A API NFE.io pode não ter spec público - - **FALLBACK**: Criar manualmente baseado no código v2 e documentação - - Script: `scripts/download-openapi.ts` - - Validação: `scripts/validate-spec.ts` - -3. ✅ Geração inicial de código - - Usar `openapi-typescript` para gerar types - - Script: `scripts/generate-sdk.ts` - - Verificar tipos gerados compilam - -**Validação**: -```bash -npm run download-spec # Spec baixado ou criado manualmente -npm run validate-spec # Spec válido -npm run generate # Código gerado -npm run typecheck # Zero erros -``` - ---- - -#### Sprint 2: Runtime Layer -**Objetivo**: HTTP client funcional com retry e rate limiting - -**Tarefas**: -1. ✅ HTTP Client (`src/runtime/http-client.ts`) - - Fetch API nativo (Node 18+) - - Autenticação via Basic Auth - - Timeout configurável - - Tratamento de 202, 204, 4xx, 5xx - -2. ✅ Retry Logic (`src/runtime/retry.ts`) - - Exponential backoff - - Configurável (maxRetries, baseDelay) - - Retry apenas em erros retryable - -3. ✅ Sistema de Erros (`src/errors/`) - - Hierarquia: NfeError → ValidationError, AuthenticationError, etc. - - Factory de erros por status HTTP - - Tipos exportáveis - -4. ✅ Rate Limiter (`src/runtime/rate-limiter.ts`) - - Controle de concorrência - - Intervalo mínimo entre requests - - Queue de requests - -**Validação**: -```bash -npm test tests/unit/runtime/ # Todos passando -npm run typecheck # Zero erros -``` - ---- - -### 🟡 IMPORTANTE - Implementar SEGUNDO (Dias 6-12) - -#### Sprint 3: Core Resources Implementation -**Objetivo**: Recursos principais do SDK completos e funcionais - -**Tarefas em ordem**: -1. ✅ NfeClient principal (`src/core/client.ts`) - - Constructor com opções (apiKey, environment, timeout, etc.) - - Instancia todos os resources - - Configuração de baseUrl por environment - -2. ✅ ServiceInvoices (`src/core/resources/service-invoices.ts`) - - **PRIORIDADE MÁXIMA** - Recurso mais usado - - create() com suporte a 202 + polling - - list() com paginação manual - - retrieve(), cancel(), sendEmail() - - downloadPDF(), downloadXML() - - createAndWait() para polling automático - -3. ✅ Companies (`src/core/resources/companies.ts`) - - CRUD completo - - uploadCertificate() com FormData - -4. ⏳ LegalPeople (`src/core/resources/legal-people.ts`) - - CRUD scoped por company_id - - Seguir padrão do ServiceInvoices - -5. ⏳ NaturalPeople (`src/core/resources/natural-people.ts`) - - CRUD scoped por company_id - - Seguir padrão do ServiceInvoices - -6. ⏳ Webhooks (`src/core/resources/webhooks.ts`) - - CRUD básico - - validate() signature para segurança - -**Validação**: -```bash -npm test tests/integration/ # Todos passando -npm run build # Exports corretos -``` - -**Exemplo de uso esperado**: -```typescript -const nfe = new NfeClient({ apiKey: 'xxx' }); -const result = await nfe.serviceInvoices.create('company-id', data); -if (result.status === 'pending') { - const invoice = await result.waitForCompletion(); -} -``` - ---- - -### 🟢 IMPORTANTE - Implementar TERCEIRO (Dias 13-18) - -#### Sprint 4: Extensibility & Testing - -**Tarefas**: -1. ⏳ Preparar SDK para extensibilidade - - Exports públicos bem definidos - - JSDoc completo em todas as APIs públicas - - CONTRIBUTING.md com guidelines para extensões - - Documentar como outros packages podem usar o SDK - -2. ⏳ Testes unitários completos - - Cobertura > 80% - - Todos os resources - - Error handling - - Retry logic - - Mocks com Vitest - -3. ⏳ Testes de integração com MSW - - Simular API completa - - Casos de erro e edge cases - - Async invoice processing - -4. ⏳ Documentação completa - - README.md atualizado com exemplos v3 - - Migration guide v2 → v3 - - API reference completa - - Seção sobre extensões oficiais (MCP, n8n) - - Examples/ com código funcional - ---- - -### 🔵 POLIMENTO - Implementar POR ÚLTIMO (Dias 19-22) - -#### Sprint 5: CI/CD & Release - -**Tarefas**: -1. ⏳ CI/CD Pipeline - - GitHub Actions para testes automáticos - - TypeScript compilation check - - Linting e formatting - - Coverage report - -2. ⏳ NPM Publish Setup - - Automated versioning - - Release notes automáticas - - Badges (build, coverage, version) - -3. ⏳ Final polish - - CHANGELOG.md completo - - Preparar v3.0.0 stable release - - Double-check backward compatibility warnings - -**Validação**: -```bash -npm test -- --coverage # Coverage > 80% -npm run docs # Docs geradas -``` - ---- - -## 🔧 Configurações Essenciais - -### package.json Obrigatório -```json -{ - "name": "nfe-io", - "version": "3.0.0-beta.1", - "description": "Official NFe.io SDK for Node.js 18+", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "require": "./dist/index.js", - "import": "./dist/index.mjs", - "types": "./dist/index.d.ts" - } - }, - "engines": { - "node": ">=18.0.0" - }, - "scripts": { - "download-spec": "tsx scripts/download-openapi.ts", - "validate-spec": "tsx scripts/validate-spec.ts", - "generate": "tsx scripts/generate-sdk.ts", - "build": "npm run generate && tsup", - "test": "vitest", - "test:coverage": "vitest --coverage", - "lint": "eslint src --ext .ts", - "format": "prettier --write 'src/**/*.ts'", - "typecheck": "tsc --noEmit", - "prepublishOnly": "npm run build && npm test" - }, - "keywords": ["nfe", "nfse", "nota-fiscal", "invoice", "brazil"], - "author": "NFE.io", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/nfe/client-nodejs.git" - } -} -``` - -### tsconfig.json Obrigatório -```json -{ - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "lib": ["ES2020"], - "moduleResolution": "bundler", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] -} -``` - -### tsup.config.ts -```typescript -import { defineConfig } from 'tsup'; - -export default defineConfig({ - entry: ['src/index.ts'], - format: ['cjs', 'esm'], - dts: true, - splitting: false, - sourcemap: true, - clean: true, - treeshake: true, - minify: true, - external: [], // Zero dependencies -}); -``` - ---- - -## 🚨 Problemas Conhecidos e Soluções - -### ⚠️ OpenAPI Spec Não Disponível Publicamente -**Problema**: NFE.io pode não ter spec OpenAPI público - -**Solução**: -1. **Tentar**: `curl https://api.nfe.io/openapi.json` -2. **Se falhar**: Criar spec manualmente baseado em: - - Código atual v2 (resources/*.js) - - [Documentação oficial](https://nfe.io/docs/) - - Análise dos samples/ - -**Estrutura mínima do spec**: -```yaml -openapi: 3.0.0 -info: - title: NFE.io API - version: 1.0.0 -servers: - - url: https://api.nfe.io/v1 -paths: - /companies/{company_id}/serviceinvoices: - post: - operationId: createServiceInvoice - # ... parameters, requestBody, responses -``` - -### ⚠️ FormData para Upload de Certificado -**Problema**: Node 18 não tem FormData nativo compatível - -**Solução**: -```bash -npm install form-data@^4.0.0 -``` - -```typescript -import FormData from 'form-data'; - -// Em Companies.uploadCertificate() -const form = new FormData(); -form.append('file', fileBuffer, 'certificate.pfx'); -form.append('password', password); -``` - -### ⚠️ Fetch API e Streams -**Problema**: Download de PDF/XML requer streaming - -**Solução**: -```typescript -async downloadPDF(companyId: string, invoiceId: string): Promise { - const response = await this.http.request( - `/companies/${companyId}/serviceinvoices/${invoiceId}/pdf`, - { headers: { Accept: 'application/pdf' } } - ); - - const arrayBuffer = await response.arrayBuffer(); - return Buffer.from(arrayBuffer); -} -``` - ---- - -## 🎯 Critérios de Sucesso por Sprint - -### Sprint 1: Fundação -- [ ] `npm run typecheck` passa -- [ ] `npm run build` gera dist/ -- [ ] OpenAPI spec existe (baixado ou manual) -- [ ] Código gerado compila - -### Sprint 2: Runtime -- [ ] HttpClient faz requests reais -- [ ] Retry funciona com exponential backoff -- [ ] Erros tipados funcionam -- [ ] Testes unitários passam - -### Sprint 3: DX Layer -- [ ] `const nfe = new NfeClient({ apiKey })` funciona -- [ ] `await nfe.serviceInvoices.create()` funciona -- [ ] Async iteration funciona -- [ ] Polling automático funciona - -### Sprint 4: Polish -- [ ] Coverage > 80% -- [ ] README completo -- [ ] CI pipeline verde -- [ ] Examples funcionam - ---- - -## 📝 Template de Commit - -``` -(): - -[optional body] - -[optional footer] -``` - -**Types**: -- `feat`: Nova feature -- `fix`: Bug fix -- `docs`: Documentação -- `test`: Testes -- `refactor`: Refatoração -- `chore`: Manutenção - -**Exemplos**: -```bash -git commit -m "feat(client): adiciona NfeClient com configuração de environment" -git commit -m "fix(retry): corrige exponential backoff com jitter" -git commit -m "docs(readme): adiciona exemplos de uso básico" -git commit -m "test(invoices): adiciona testes de integração para create" -``` - ---- - -## 🤖 Instruções Finais para Execução Autônoma - -### Quando Executar Automaticamente -✅ Setup inicial do projeto -✅ Geração de código do OpenAPI -✅ Implementação de resources seguindo padrões -✅ Escrita de testes unitários -✅ Configuração de CI/CD -✅ Geração de documentação - -### Quando Pedir Intervenção Humana -❌ OpenAPI spec não encontrado (precisa ser criado manualmente) -❌ Decisões de breaking changes na API pública -❌ Credenciais para testes E2E ou publicação NPM -❌ Validação final antes do release - -### Validação Contínua -Após CADA arquivo criado/modificado: -```bash -npm run typecheck && npm run lint && npm test -``` - -Se qualquer comando falhar, **PARE** e corrija antes de continuar. - ---- - -## � Extensões Oficiais em Repositórios Separados - -O SDK NFE.io v3 foi projetado para ser extensível. As seguintes extensões oficiais estão em repositórios separados: - -### [@nfe-io/mcp-server](https://github.com/nfe/mcp-server) -**Model Context Protocol Server para integração com LLMs** - -- Permite que LLMs (Claude, GPT, etc.) emitam notas fiscais via conversação natural -- Implementa MCP tools usando `nfe-io` internamente -- Instale: `npm install @nfe-io/mcp-server` -- Depende de: `nfe-io` (peer dependency) - -### [@nfe-io/n8n-nodes](https://github.com/nfe/n8n-nodes) -**Custom nodes n8n para automação de workflows** - -- Permite automação de emissão de notas fiscais em workflows n8n -- Nodes para ServiceInvoices, Companies, Webhooks -- Instale via n8n community nodes ou `npm install @nfe-io/n8n-nodes` -- Depende de: `nfe-io` (dependency) - -### Criando Sua Própria Extensão - -Veja [CONTRIBUTING.md](./CONTRIBUTING.md) para guidelines sobre como criar extensões usando o SDK. - ---- - -## �📚 Referências Essenciais - -- **Documentação API**: https://nfe.io/docs/ -- **Código v2**: Arquivos atuais deste projeto -- **OpenAPI Spec**: https://swagger.io/specification/ -- **TypeScript**: https://www.typescriptlang.org/docs/ -- **Vitest**: https://vitest.dev/ -- **MSW**: https://mswjs.io/ - ---- - -**Boa implementação! 🚀** diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6d7516d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Official NFE.io SDK for Node.js -- TypeScript native client for Brazilian electronic fiscal document APIs (NFS-e, NF-e, CT-e, CF-e). Version 3.x is a complete rewrite from the legacy v2 JavaScript/callback codebase that still lives in `lib/`. + +## Build & Development Commands + +```bash +npm run validate:spec # Validate OpenAPI YAML specs +npm run generate # Generate TypeScript types from OpenAPI specs into src/generated/ +npm run build # Full pipeline: validate:spec -> generate -> clean -> typecheck -> tsup +npm run typecheck # TypeScript strict check (tsc --noEmit) +npm run lint # ESLint with auto-fix +npm run format # Prettier formatting +``` + +## Test Commands + +```bash +npm test # Run all tests (vitest, watch mode) +npm test -- --run # Run all tests once (no watch) +npm run test:unit # Unit tests only (tests/unit/) +npm run test:integration # Integration tests only (tests/integration/) +npm run test:coverage # Tests with v8 coverage report +npm test -- tests/unit/specific-file.test.ts # Run a single test file +``` + +Coverage thresholds: 80% for branches, functions, lines, and statements. Test setup is in `tests/setup.ts` with mock data generators and test constants. + +## Architecture + +### Dual Codebase (v2 + v3) + +- **v3 (active)**: `src/` -- TypeScript, async/await, Fetch API, zero runtime dependencies +- **v2 (legacy)**: `lib/` -- JavaScript, `when` promises, `BaseResource.extend()` pattern + +### v3 Source Structure + +- `src/index.ts` -- Barrel export for all public API (`NfeClient`, types, errors) +- `src/core/client.ts` -- Main `NfeClient` class with lazy-initialized resource getters and polling utilities +- `src/core/types.ts` -- All TypeScript type definitions (config, resources, HTTP) +- `src/core/http/client.ts` -- Fetch-based HTTP client with retry, timeout, rate-limit handling +- `src/core/errors/` -- Error hierarchy: `NfeError` base, then `AuthenticationError`, `ValidationError`, `NotFoundError`, `RateLimitError`, `TimeoutError`, `ConnectionError`, etc. +- `src/core/resources/` -- 17 resource implementations (service-invoices, companies, webhooks, etc.) +- `src/generated/` -- **AUTO-GENERATED from OpenAPI specs. NEVER edit manually.** + +### OpenAPI Pipeline + +Specs live in `openapi/spec/*.yaml`. The generation script (`scripts/generate-types.ts`) produces typed interfaces in `src/generated/`. The build pipeline always validates and regenerates before compiling. + +### Key Patterns + +- **Company-scoped operations**: Most resource methods take `company_id` as first parameter +- **Async invoice processing**: `serviceInvoices.create()` may return 202 + Location header; use polling utilities (`pollUntilComplete`, `pollWithExponentialBackoff`) for completion +- **Discriminated union returns**: Create operations return typed results distinguishing immediate (201) vs async (202) responses +- **Lazy resource initialization**: Resources on `NfeClient` are instantiated on first property access +- **Multiple API endpoints**: Separate HTTP clients for different service APIs (main, CT-e, legal entity, natural person, etc.) + +### Module Output + +Dual format via tsup: ESM (`dist/index.js`) + CommonJS (`dist/index.cjs`) + type declarations (`dist/index.d.ts`). Target: Node.js 18+. + +## Code Style + +- TypeScript strict mode with `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes` +- Prettier: single quotes, trailing commas (ES5), 100 char width, semicolons +- ESLint: no `any` types (warning), unused vars allowed with `_` prefix +- No runtime dependencies -- everything uses Node.js built-ins (Fetch, AbortController, Buffer) + +## Important Rules + +- **Never edit files in `src/generated/`** -- they are overwritten by the generation pipeline +- Run `npm run generate` after modifying any OpenAPI spec in `openapi/spec/` +- Typecheck and tests must pass before commits (`npm run typecheck && npm test -- --run`) +- The CHANGELOG is written in Portuguese diff --git a/FILE_CONFIGURATION.md b/FILE_CONFIGURATION.md deleted file mode 100644 index 5fd8dd6..0000000 --- a/FILE_CONFIGURATION.md +++ /dev/null @@ -1,644 +0,0 @@ -# 📁 Configuração de Arquivos - NFE.io SDK v3 - -Este documento descreve a configuração de arquivos de controle do projeto para o SDK v3. - -## � Estrutura de Diretórios - -``` -client-nodejs/ -├── .github/ -│ └── workflows/ # GitHub Actions CI/CD -│ ├── ci.yml # Testes e validação -│ └── publish.yml # Publicação no NPM -├── dist/ # ⚠️ Gerado pelo build (não versionado) -│ ├── index.js # ESM bundle -│ ├── index.cjs # CommonJS bundle -│ ├── index.d.ts # TypeScript definitions (ESM) -│ ├── index.d.cts # TypeScript definitions (CJS) -│ └── *.map # Source maps -├── src/ # Código-fonte TypeScript -│ ├── core/ # Core do SDK -│ │ ├── client.ts # NfeClient principal -│ │ ├── types.ts # Tipos TypeScript -│ │ ├── errors/ # Sistema de erros -│ │ ├── http/ # HTTP client layer -│ │ ├── resources/ # API Resources (Companies, ServiceInvoices, etc) -│ │ └── utils/ # Utilitários (validações, certificados) -│ ├── generated/ # ⚠️ Auto-gerado do OpenAPI (não editar) -│ │ ├── nf-servico-v1.ts -│ │ └── *.ts # Tipos de outras APIs -│ └── index.ts # Exports públicos -├── tests/ -│ ├── unit/ # Testes unitários -│ ├── integration/ # Testes de integração -│ └── setup.ts # Setup dos testes -├── openapi/ -│ ├── spec/ # Especificações OpenAPI -│ └── generator-config.yaml -├── scripts/ # Scripts de desenvolvimento -│ ├── generate-types.ts # Geração de tipos do OpenAPI -│ ├── validate-spec.ts # Validação das specs -│ └── download-openapi.ts -├── examples/ # Exemplos de uso -├── docs/ # Documentação técnica -├── coverage/ # ⚠️ Gerado pelos testes (não versionado) -└── logs/ # ⚠️ Logs do projeto (não versionado) -``` - -## �📋 Arquivos de Configuração - -### `.gitignore` -**Propósito**: Define quais arquivos/diretórios o Git deve ignorar. - -**Principais exclusões**: -- ✅ `node_modules/` - Dependências (instaladas via npm) -- ✅ `dist/` - Código compilado (gerado pelo build) -- ✅ `coverage/` - Relatórios de cobertura de testes -- ✅ `*.tgz` - Pacotes NPM gerados -- ✅ `.env*` - Variáveis de ambiente -- ✅ `logs/` - Arquivos de log do projeto -- ✅ `*.log` - Arquivos de log (npm-debug.log, yarn-error.log, etc) -- ✅ IDE configs - `.vscode/`, `.idea/`, `*.iml` -- ✅ OS files - `.DS_Store`, `Thumbs.db`, `ehthumbs.db` -- ✅ Build artifacts - `*.tsbuildinfo`, `buildAssets/` -- ✅ Coverage - `.nyc_output/`, `*.lcov` - -**O que é versionado**: -- ✅ `src/` - Código-fonte TypeScript -- ✅ `tests/` - Testes unitários e de integração -- ✅ `openapi/` - Especificações OpenAPI e gerador -- ✅ `scripts/` - Scripts de build e validação -- ✅ Arquivos de configuração (`.eslintrc.cjs`, `tsconfig.json`, `tsup.config.ts`, etc) -- ✅ Documentação (`README.md`, `CHANGELOG.md`, `MIGRATION.md`, etc) -- ✅ GitHub Actions (`.github/workflows/`) -- ✅ Examples (`examples/`) - Exemplos de uso do SDK - -### `.npmignore` -**Propósito**: Define o que **não** será publicado no NPM. - -**Excluído do pacote NPM**: -- ❌ `src/` - Código-fonte TypeScript (publicamos apenas `dist/`) -- ❌ `tests/` - Testes unitários e de integração -- ❌ `examples/` - Exemplos de código -- ❌ `scripts/` - Scripts de desenvolvimento -- ❌ `openapi/` - Especificações OpenAPI e configuração do gerador -- ❌ `docs/` - Documentação interna do projeto -- ❌ Configs de desenvolvimento (`.eslintrc.cjs`, `tsconfig.json`, `vitest.config.ts`, etc) -- ❌ Documentação interna (`AGENTS.md`, `CONTRIBUTING.md`, `FILE_CONFIGURATION.md`, etc) -- ❌ CI/CD configs (`.github/`, workflows) -- ❌ Arquivos legados (`lib/`, `samples/`, `VERSION`) -- ❌ Logs e temporários (`logs/`, `*.log`, `.env*`) - -**Incluído no pacote NPM** (via `package.json` "files"): -- ✅ `dist/` - Código compilado (ESM + CommonJS + Types) -- ✅ `README.md` - Documentação principal -- ✅ `CHANGELOG.md` - Histórico de versões -- ✅ `MIGRATION.md` - Guia de migração v2→v3 -- ✅ `package.json` - Metadados do pacote -- ✅ `LICENSE` (se presente) - -### `.gitattributes` -**Propósito**: Controla como o Git trata diferentes tipos de arquivo. - -**Configurações**: -- ✅ **Line endings**: LF para código (`*.ts`, `*.js`, `*.json`) -- ✅ **PowerShell**: CRLF para `*.ps1` (Windows) -- ✅ **Diff patterns**: TypeScript, JavaScript, JSON, Markdown -- ✅ **Binary files**: Imagens, fontes, arquivos compactados -- ✅ **Export-ignore**: Arquivos de dev não incluídos em archives -- ✅ **Merge strategies**: `package-lock.json` usa merge=ours - -### `.editorconfig` -**Propósito**: Mantém estilo de código consistente entre editores. - -**Configurações**: -- ✅ **Charset**: UTF-8 -- ✅ **Indentação**: 2 espaços (TypeScript, JavaScript, JSON) -- ✅ **Line endings**: LF (exceto PowerShell = CRLF) -- ✅ **Trim trailing whitespace**: Sim -- ✅ **Insert final newline**: Sim -- ✅ **Max line length**: 100 (TypeScript/JavaScript) - -### `package.json` - Campo "files" -**Propósito**: Lista explícita de arquivos/diretórios publicados no NPM. - -```json -{ - "files": [ - "dist", // Código compilado - "README.md", // Documentação - "CHANGELOG.md", // Release notes - "MIGRATION.md" // Guia v2→v3 - ] -} -``` - -### `tsconfig.json` -**Propósito**: Configuração do compilador TypeScript. - -**Principais configurações**: -- ✅ **Target**: ES2020 (Node.js 18+) -- ✅ **Module**: ESNext (com moduleResolution: bundler) -- ✅ **Strict mode**: Habilitado (máxima segurança de tipos) -- ✅ **Declarations**: Gera arquivos `.d.ts` automaticamente -- ✅ **Source maps**: Habilitado para debugging -- ✅ **RootDir**: `./src` (entrada) -- ✅ **OutDir**: `./dist` (saída - apenas para typecheck, build real usa tsup) - -### `tsup.config.ts` -**Propósito**: Configuração do bundler de produção. - -**Principais configurações**: -- ✅ **Entry**: `src/index.ts` -- ✅ **Formats**: `['cjs', 'esm']` (dual package) -- ✅ **DTS**: `true` (gera `.d.ts` e `.d.cts`) -- ✅ **Sourcemap**: `true` (inclui `.map` files) -- ✅ **Minify**: `true` (código otimizado) -- ✅ **Treeshake**: `true` (remove código não usado) -- ✅ **Clean**: `true` (limpa dist/ antes do build) -- ✅ **Target**: `node18` (compatibilidade) - -### `vitest.config.ts` -**Propósito**: Configuração do framework de testes. - -**Principais configurações**: -- ✅ **Coverage**: v8 provider com threshold de 80% -- ✅ **Globals**: `false` (imports explícitos) -- ✅ **Environment**: `node` -- ✅ **Include**: `tests/**/*.test.ts` -- ✅ **Exclude**: `node_modules/`, `dist/`, `coverage/` -- ✅ **Timeout**: 10000ms para testes de integração - -### `.eslintrc.cjs` -**Propósito**: Regras de linting e formatação de código. - -**Principais configurações**: -- ✅ **Parser**: `@typescript-eslint/parser` -- ✅ **Extends**: TypeScript recommended + Prettier -- ✅ **Rules**: Personalizadas para o projeto -- ✅ **Env**: Node.js + ES2020 - -## 📊 Tamanho do Pacote NPM - -``` -Arquivo Tamanho -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -dist/index.js 85.8 KB (ESM) -dist/index.cjs 87.6 KB (CommonJS) -dist/index.d.ts 56.3 KB (TypeScript types ESM) -dist/index.d.cts 56.3 KB (TypeScript types CJS) -dist/*.map 328.0 KB (Source maps) -README.md 15.6 KB -CHANGELOG.md 5.2 KB -MIGRATION.md 17.7 KB -package.json 2.7 KB -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Total (tarball) 134.2 KB -Total (unpacked) 670.7 KB -Total files 10 -``` - -## ✅ Validação - -### Verificar o que será publicado no NPM -```bash -npm pack --dry-run -``` - -**Saída esperada**: -``` -npm notice 📦 nfe-io@3.0.0 -npm notice Tarball Contents -npm notice 5.2kB CHANGELOG.md -npm notice 17.7kB MIGRATION.md -npm notice 15.6kB README.md -npm notice 87.6kB dist/index.cjs -npm notice 164.3kB dist/index.cjs.map -npm notice 56.3kB dist/index.d.cts -npm notice 56.3kB dist/index.d.ts -npm notice 85.8kB dist/index.js -npm notice 163.7kB dist/index.js.map -npm notice 2.7kB package.json -npm notice Tarball Details -npm notice name: nfe-io -npm notice version: 3.0.0 -npm notice package size: 134.2 kB -npm notice unpacked size: 670.7 kB -npm notice total files: 10 -``` - -### Testar instalação local -```bash -# 1. Criar tarball -npm pack - -# 2. Instalar em projeto teste -cd ../test-project -npm install ../client-nodejs/nfe-io-3.0.0.tgz - -# 3. Verificar imports ESM -node --input-type=module --eval "import { NfeClient } from 'nfe-io'; console.log('✅ ESM OK');" - -# 4. Verificar imports CommonJS -node --input-type=commonjs --eval "const { NfeClient } = require('nfe-io'); console.log('✅ CJS OK');" - -# 5. Verificar tipos TypeScript -echo "import { NfeClient } from 'nfe-io';" > test.ts -npx tsc --noEmit test.ts && echo "✅ Types OK" -``` - -### Verificar arquivos ignorados pelo Git -```bash -git status --ignored -``` - -## 🎯 Comparação v2 vs v3 - -| Aspecto | v2 (Legado) | v3 (Atual) | -|---------|-------------|------------| -| **Código publicado** | `lib/*.js` | `dist/*.{js,cjs,d.ts,d.cts}` | -| **Line endings** | Inconsistente | LF (via .gitattributes) | -| **Indentação** | Mista | 2 espaços (via .editorconfig) | -| **Docs incluídas** | README | README + CHANGELOG + MIGRATION | -| **Source maps** | ❌ Não | ✅ Sim (.map files) | -| **TypeScript types** | ❌ Não | ✅ Sim (.d.ts + .d.cts) | -| **Dual package** | ❌ Não | ✅ ESM + CommonJS | -| **OpenAPI types** | ❌ Não | ✅ Sim (7 specs gerados) | -| **Tamanho tarball** | ~50 KB | 134.2 KB (+docs +types +source maps) | -| **Total files** | ~5 | 10 | - -## 🔍 Troubleshooting - -### Arquivo não ignorado pelo Git -```bash -# Remover arquivo do cache do Git -git rm --cached path/to/file - -# Re-adicionar respeitando .gitignore -git add . -``` - -### Arquivo indesejado no pacote NPM -1. Verificar `.npmignore` -2. Verificar campo `"files"` no `package.json` -3. Testar: `npm pack --dry-run` - -### Line endings incorretos -```bash -# Re-normalizar todos os arquivos -git add --renormalize . -git commit -m "Normalize line endings" -``` - -### EditorConfig não funcionando -- Instalar plugin EditorConfig no seu editor -- VSCode: `EditorConfig for VS Code` -- JetBrains: Built-in -- Vim: `editorconfig-vim` - -### Build artifacts incorretos -```bash -# Limpar completamente e rebuildar -npm run clean -rm -rf node_modules package-lock.json -npm install -npm run build - -# Verificar arquivos gerados -ls -lh dist/ -``` - -### Testes falhando antes de publicar -```bash -# Rodar apenas testes unitários (ignorar integração que precisa de API key) -npm test -- --run tests/unit - -# Se testes de integração falharem, verifique: -# - Variável de ambiente NFE_API_KEY está definida? -# - API está acessível? -``` - -## � Secrets e Variáveis de Ambiente - -### Desenvolvimento Local -```bash -# .env (não versionado - criar localmente) -NFE_API_KEY=your-api-key-here -NFE_ENVIRONMENT=development - -# Usar em testes de integração -# Os testes checam se NFE_API_KEY existe antes de rodar -``` - -### GitHub Actions -**Secrets necessários** (configurar em Settings > Secrets): -- `NPM_TOKEN` - Token de publicação no NPM (obrigatório para publish) - -**Variables opcionais**: -- Nenhuma necessária no momento - -### Como Configurar Secrets no GitHub -1. Acesse: `https://github.com/nfe/client-nodejs/settings/secrets/actions` -2. Clique em **"New repository secret"** -3. Nome: `NPM_TOKEN` -4. Valor: Token gerado no npmjs.com (formato: `npm_xxxxxxxxxxxxx`) -5. Salvar - -## 📦 Preparação para Publicação - -### Checklist Completo -```bash -# ✅ 1. Versão atualizada -cat package.json | grep version -# Deve mostrar: "version": "3.0.0" - -# ✅ 2. CHANGELOG atualizado -cat CHANGELOG.md | head -20 -# Verificar se versão 3.0.0 está documentada - -# ✅ 3. OpenAPI specs válidos -npm run validate:spec -# Deve mostrar: "✅ All specifications are valid!" - -# ✅ 4. Tipos gerados -npm run generate -# Deve gerar 7 de 12 specs - -# ✅ 5. TypeScript compila -npm run typecheck -# Deve passar sem erros - -# ✅ 6. Testes unitários passando -npm test -- --run tests/unit -# Deve mostrar: "253 passed" - -# ✅ 7. Build funciona -npm run build -# Deve gerar dist/ com 6 arquivos - -# ✅ 8. Verificar conteúdo do pacote -npm pack --dry-run -# Deve listar 10 arquivos (dist/ + docs) - -# ✅ 9. Testar instalação local -npm pack -# Gera nfe-io-3.0.0.tgz para testar -``` - -## 🚀 Processo de Publicação - -### Publicação Manual -```bash -# 1. Garantir que está na main -git checkout main -git pull origin main - -# 2. Atualizar versão (se não estiver) -npm version 3.0.0 --no-git-tag-version - -# 3. Build e validação completa -npm run build -npm test -- --run tests/unit - -# 4. Dry-run (simula publicação) -npm publish --dry-run - -# 5. Publicar (ATENÇÃO: Ação irreversível!) -npm publish --access public - -# 6. Criar tag no Git -git tag v3.0.0 -git push origin v3.0.0 - -# 7. Criar Release no GitHub -# https://github.com/nfe/client-nodejs/releases/new -``` - -### Publicação via GitHub Actions (Recomendado) -```bash -# MÉTODO 1: Via Release (Mais completo) -# ==================================== - -# 1. Criar e push tag -git tag v3.0.0 -git push origin v3.0.0 - -# 2. Criar Release no GitHub -# Acesse: https://github.com/nfe/client-nodejs/releases/new -# Preencha: -# - Choose a tag: v3.0.0 -# - Release title: v3.0.0 - [Nome da Release] -# - Description: [Cole o CHANGELOG desta versão] -# - Clique em "Publish release" - -# ✅ O workflow publish.yml será acionado automaticamente - - -# MÉTODO 2: Manual Dispatch (Mais rápido) -# ======================================== - -# 1. Acesse: https://github.com/nfe/client-nodejs/actions/workflows/publish.yml -# 2. Clique em "Run workflow" (botão à direita) -# 3. Selecione: -# - Branch: main -# - Tag to publish: v3.0.0 -# 4. Clique em "Run workflow" - -# ✅ O workflow rodará build + tests + publish - - -# O que o workflow faz automaticamente: -# - ✅ Checkout do código -# - ✅ Setup Node.js 20 -# - ✅ Install dependencies -# - ✅ Valida OpenAPI specs -# - ✅ Gera tipos TypeScript -# - ✅ Roda testes -# - ✅ Type checking -# - ✅ Build -# - ✅ Verifica artifacts -# - ✅ Dry-run -# - ✅ Publica no NPM com provenance -# - ✅ Cria summary no GitHub -``` - -### Verificar Publicação -```bash -# Ver pacote no NPM (aguardar ~1 minuto após publicar) -open https://www.npmjs.com/package/nfe-io - -# Verificar versão específica -npm view nfe-io@3.0.0 - -# Testar instalação em projeto novo -mkdir test-nfe && cd test-nfe -npm init -y -npm install nfe-io@3.0.0 - -# Verificar exports ESM -node --input-type=module -e "import {NfeClient} from 'nfe-io'; console.log('✅ ESM:', NfeClient);" - -# Verificar exports CommonJS -node -e "const {NfeClient} = require('nfe-io'); console.log('✅ CJS:', NfeClient);" - -# Verificar tipos TypeScript -echo "import { NfeClient } from 'nfe-io'; const c: NfeClient = null as any;" > test.ts -npx -y typescript tsc --noEmit test.ts && echo "✅ Types OK" -``` - -### Troubleshooting de Publicação - -#### Erro: "You must be logged in" -```bash -# Solução: Fazer login no NPM -npm login - -# Verificar usuário logado -npm whoami -``` - -#### Erro: "You do not have permission to publish 'nfe-io'" -```bash -# Solução 1: Verificar owners do pacote -npm owner ls nfe-io - -# Solução 2: Adicionar seu usuário (executar pelo owner atual) -npm owner add SEU_USUARIO nfe-io -``` - -#### Erro: "Version 3.0.0 already exists" -```bash -# Solução: Incrementar versão no package.json -npm version patch # 3.0.0 -> 3.0.1 -npm version minor # 3.0.0 -> 3.1.0 -npm version major # 3.0.0 -> 4.0.0 - -# Ou manualmente editar package.json -``` - -#### Erro no GitHub Actions: "NPM_TOKEN not found" -```bash -# Solução: Adicionar secret no GitHub -# 1. Acesse: https://github.com/nfe/client-nodejs/settings/secrets/actions -# 2. New repository secret -# 3. Name: NPM_TOKEN -# 4. Value: (token do npmjs.com) -# 5. Add secret -``` - -#### Erro: "This package has been marked as private" -```bash -# Solução: Remover "private": true do package.json -# Verificar que não existe essa linha no package.json -``` - -#### Build falha com erros TypeScript -```bash -# Solução: Limpar e rebuildar -npm run clean -rm -rf node_modules package-lock.json -npm install -npm run typecheck -npm run build -``` - -#### Testes falhando no CI -```bash -# Solução: Rodar apenas testes unitários -# O workflow já está configurado para ignorar testes de integração -# que precisam de API key real - -# Verificar localmente: -npm test -- --run tests/unit - -# Se falhar localmente, debugar: -npm test -- --run tests/unit/companies.test.ts -``` - -## �️ Manutenção Contínua - -### Atualizando Dependências -```bash -# Verificar dependências desatualizadas -npm outdated - -# Atualizar dependências de desenvolvimento -npm update --save-dev - -# Atualizar major versions (com cuidado) -npx npm-check-updates -u -npm install - -# Rodar testes após atualizar -npm test -- --run -``` - -### Regenerando Tipos do OpenAPI -```bash -# Quando specs OpenAPI mudarem -npm run validate:spec -npm run generate - -# Commit changes -git add src/generated/ -git commit -m "chore: regenerate OpenAPI types" -``` - -### Mantendo .gitignore Limpo -```bash -# Ver arquivos ignorados -git status --ignored - -# Limpar arquivos desnecessários -git clean -xdn # Dry-run (mostra o que seria removido) -git clean -xdf # Remove (cuidado!) -``` - -### Monitorando Tamanho do Pacote -```bash -# Verificar tamanho atual -npm pack --dry-run | grep "package size" - -# Analisar o que contribui para o tamanho -npx package-size nfe-io - -# Objetivo: Manter < 150 KB (tarball) -``` - -## �📚 Referências - -### Documentação Oficial -- **Git Ignore**: https://git-scm.com/docs/gitignore -- **NPM Files**: https://docs.npmjs.com/cli/v9/using-npm/developers#keeping-files-out-of-your-package -- **NPM Publish**: https://docs.npmjs.com/cli/v9/commands/npm-publish -- **EditorConfig**: https://editorconfig.org/ -- **Git Attributes**: https://git-scm.com/docs/gitattributes -- **TypeScript Config**: https://www.typescriptlang.org/tsconfig -- **Tsup**: https://tsup.egoist.dev/ -- **Vitest**: https://vitest.dev/ - -### Ferramentas Úteis -- **npm-check-updates**: Atualizar dependências -- **package-size**: Analisar tamanho do pacote -- **size-limit**: Limitar tamanho do bundle -- **publint**: Validar configuração de publicação - -### Recursos do Projeto -- **GitHub Repo**: https://github.com/nfe/client-nodejs -- **NPM Package**: https://www.npmjs.com/package/nfe-io -- **Issues**: https://github.com/nfe/client-nodejs/issues -- **Releases**: https://github.com/nfe/client-nodejs/releases -- **CI/CD**: https://github.com/nfe/client-nodejs/actions - -### Documentação Interna -- [README.md](./README.md) - Guia principal -- [CHANGELOG.md](./CHANGELOG.md) - Histórico de versões -- [MIGRATION.md](./MIGRATION.md) - Guia de migração v2→v3 -- [CONTRIBUTING.md](./CONTRIBUTING.md) - Guia para contribuidores -- [AGENTS.md](./AGENTS.md) - Instruções para AI agents - ---- - -**Última atualização**: 2026-01-13 -**Versão**: 3.0.0 -**Status**: ✅ Pronto para publicação diff --git a/RELEASE_COMMANDS.ps1 b/RELEASE_COMMANDS.ps1 deleted file mode 100644 index 02998b4..0000000 --- a/RELEASE_COMMANDS.ps1 +++ /dev/null @@ -1,191 +0,0 @@ -# NFE.io SDK v3.0.0 - Release Commands (PowerShell) -# -# Este arquivo contém todos os comandos necessários para -# completar o release do SDK v3.0.0 -# -# Uso: Execute os blocos na ordem ou use .\scripts\release.ps1 - -$ErrorActionPreference = "Stop" - -Write-Host "🚀 NFE.io SDK v3.0.0 - Release Commands" -ForegroundColor Cyan -Write-Host "========================================`n" -ForegroundColor Cyan - -# ============================================================================ -# BLOCO 1: VALIDAÇÃO PRÉ-RELEASE -# ============================================================================ -Write-Host "📋 BLOCO 1: Validação Pré-Release" -ForegroundColor Yellow -Write-Host "----------------------------------`n" -ForegroundColor Yellow - -# Verificar status git -Write-Host "▸ Verificando status git..." -ForegroundColor Gray -git status - -# TypeCheck -Write-Host "`n▸ TypeScript compilation check..." -ForegroundColor Gray -npm run typecheck - -# Build -Write-Host "`n▸ Build final..." -ForegroundColor Gray -npm run build - -# Verificar package -Write-Host "`n▸ Verificando conteúdo do package..." -ForegroundColor Gray -npm pack --dry-run - -Write-Host "`n✅ BLOCO 1 completo!`n" -ForegroundColor Green - -# ============================================================================ -# BLOCO 2: GIT COMMIT & TAG -# ============================================================================ -Write-Host "📝 BLOCO 2: Git Commit & Tag" -ForegroundColor Yellow -Write-Host "----------------------------`n" -ForegroundColor Yellow - -$commitMessage = @" -Release v3.0.0 - -- Complete TypeScript rewrite with zero runtime dependencies -- Modern async/await API with full type safety -- 5 core resources: ServiceInvoices, Companies, LegalPeople, NaturalPeople, Webhooks -- 107 tests passing (88% coverage) -- Dual ESM/CommonJS support -- Node.js 18+ required (for native fetch API) -- Comprehensive documentation (README, MIGRATION, CHANGELOG) - -Breaking changes: -- Package renamed: nfe → @nfe-io/sdk -- Minimum Node.js: 12 → 18 -- API changed: callbacks → async/await -- Removed: when dependency (using native promises) - -See MIGRATION.md for complete v2→v3 migration guide. -See CHANGELOG.md for detailed release notes. -"@ - -$tagMessage = @" -Release v3.0.0 - Complete TypeScript Rewrite - -Major version with full TypeScript rewrite, zero dependencies, and modern async/await API. - -Highlights: -- 🎯 TypeScript 5.3+ with strict mode -- 📦 Zero runtime dependencies -- 🚀 Native fetch API (Node.js 18+) -- ✅ 107 tests (88% coverage) -- 📚 Complete documentation suite -- 🔄 Dual ESM/CommonJS support - -Breaking Changes: -See MIGRATION.md for migration guide from v2. - -Full changelog: https://github.com/nfe/client-nodejs/blob/v3/CHANGELOG.md -"@ - -Write-Host "Comandos Git:" -ForegroundColor Cyan -Write-Host "git add ." -ForegroundColor White -Write-Host "git commit -m `"...(mensagem acima)...`"" -ForegroundColor White -Write-Host "git tag v3.0.0 -a -m `"...(mensagem acima)...`"" -ForegroundColor White -Write-Host "git push origin v3" -ForegroundColor White -Write-Host "git push origin v3.0.0`n" -ForegroundColor White - -$confirm = Read-Host "Executar comandos git agora? (y/N)" -if ($confirm -eq 'y' -or $confirm -eq 'Y') { - Write-Host "`n▸ git add..." -ForegroundColor Gray - git add . - - Write-Host "▸ git commit..." -ForegroundColor Gray - git commit -m $commitMessage - - Write-Host "▸ git tag..." -ForegroundColor Gray - git tag v3.0.0 -a -m $tagMessage - - Write-Host "▸ git push..." -ForegroundColor Gray - git push origin v3 - git push origin v3.0.0 - - Write-Host "`n✅ BLOCO 2 completo!`n" -ForegroundColor Green -} -else { - Write-Host "`n⏭️ BLOCO 2 pulado (execute comandos manualmente)`n" -ForegroundColor Yellow -} - -# ============================================================================ -# BLOCO 3: NPM PUBLISH -# ============================================================================ -Write-Host "📦 BLOCO 3: NPM Publish" -ForegroundColor Yellow -Write-Host "-----------------------`n" -ForegroundColor Yellow - -# Verificar login -Write-Host "▸ Verificando npm login..." -ForegroundColor Gray -try { - npm whoami -} -catch { - Write-Host "❌ Não logado no NPM! Execute: npm login" -ForegroundColor Red - exit 1 -} - -# Dry-run -Write-Host "`n▸ NPM publish dry-run..." -ForegroundColor Gray -npm publish --dry-run - -# Confirmação -Write-Host "`n⚠️ ATENÇÃO: Você está prestes a publicar @nfe-io/sdk@3.0.0 para NPM!" -ForegroundColor Yellow -Write-Host " Isso é irreversível!`n" -ForegroundColor Yellow - -$confirmPublish = Read-Host "Continuar com publicação? (y/N)" -if ($confirmPublish -eq 'y' -or $confirmPublish -eq 'Y') { - # Publish real - Write-Host "`n▸ Publicando para NPM..." -ForegroundColor Gray - npm publish --access public - - # Verificar publicação - Write-Host "`n▸ Verificando publicação..." -ForegroundColor Gray - npm view @nfe-io/sdk version - npm view @nfe-io/sdk dist-tags - - Write-Host "`n✅ BLOCO 3 completo!`n" -ForegroundColor Green -} -else { - Write-Host "`n❌ Publicação cancelada pelo usuário`n" -ForegroundColor Red -} - -# ============================================================================ -# BLOCO 4: PÓS-RELEASE -# ============================================================================ -Write-Host "🎉 BLOCO 4: Pós-Release" -ForegroundColor Yellow -Write-Host "-----------------------`n" -ForegroundColor Yellow - -Write-Host "Próximas ações manuais:`n" -ForegroundColor Cyan - -Write-Host "1. GitHub Release:" -ForegroundColor White -Write-Host " https://github.com/nfe/client-nodejs/releases/new" -ForegroundColor Gray -Write-Host " - Tag: v3.0.0" -ForegroundColor Gray -Write-Host " - Title: v3.0.0 - Complete TypeScript Rewrite" -ForegroundColor Gray -Write-Host " - Description: Copiar de CHANGELOG.md`n" -ForegroundColor Gray - -Write-Host "2. Atualizar website NFE.io:" -ForegroundColor White -Write-Host " - Adicionar exemplos v3 na documentação" -ForegroundColor Gray -Write-Host " - Atualizar guia de instalação" -ForegroundColor Gray -Write-Host " - Adicionar link para MIGRATION.md`n" -ForegroundColor Gray - -Write-Host "3. Anunciar release:" -ForegroundColor White -Write-Host " - Blog post" -ForegroundColor Gray -Write-Host " - Newsletter" -ForegroundColor Gray -Write-Host " - Twitter/X: @nfeio" -ForegroundColor Gray -Write-Host " - Developer community`n" -ForegroundColor Gray - -Write-Host "4. Monitorar:" -ForegroundColor White -Write-Host " - NPM downloads: https://www.npmjs.com/package/@nfe-io/sdk" -ForegroundColor Gray -Write-Host " - GitHub issues: https://github.com/nfe/client-nodejs/issues" -ForegroundColor Gray -Write-Host " - User feedback nos primeiros dias`n" -ForegroundColor Gray - -Write-Host "5. Preparar v3.1.0:" -ForegroundColor White -Write-Host " - Criar milestone no GitHub" -ForegroundColor Gray -Write-Host " - Adicionar issues para melhorias" -ForegroundColor Gray -Write-Host " - Planejar features baseado em feedback`n" -ForegroundColor Gray - -Write-Host "✅ Release v3.0.0 preparado!`n" -ForegroundColor Green - -Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan -Write-Host "🎊 Parabéns! NFE.io SDK v3.0.0 está pronto para lançamento!" -ForegroundColor Green -Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`n" -ForegroundColor Cyan diff --git a/RELEASE_SCRIPTS_COMPARISON.md b/RELEASE_SCRIPTS_COMPARISON.md deleted file mode 100644 index 63d9bb7..0000000 --- a/RELEASE_SCRIPTS_COMPARISON.md +++ /dev/null @@ -1,284 +0,0 @@ -# 🔄 Comparação de Scripts de Release - -## 📊 Resumo Executivo - -| Feature | Windows (PowerShell) | Linux/macOS (Bash) | -|---------|---------------------|-------------------| -| **Script Automatizado** | `scripts/release.ps1` | `scripts/release.sh` | -| **Script Interativo** | `RELEASE_COMMANDS.ps1` | `RELEASE_COMMANDS.sh` | -| **Cores no Output** | ✅ Sim | ✅ Sim | -| **Confirmações** | ✅ Sim | ✅ Sim | -| **Dry-Run Mode** | ✅ Sim (`-DryRun`) | ✅ Sim (`--dry-run`) | -| **Skip Tests** | ✅ Sim (`-SkipTests`) | ✅ Sim (`--skip-tests`) | -| **Skip Git** | ✅ Sim (`-SkipGit`) | ✅ Sim (`--skip-git`) | -| **Help** | ✅ Sim (`Get-Help`) | ✅ Sim (`--help`) | - -## 🎯 Scripts Automatizados - -### Windows: `scripts/release.ps1` - -```powershell -# Sintaxe -.\scripts\release.ps1 [-DryRun] [-SkipTests] [-SkipGit] - -# Exemplos -.\scripts\release.ps1 # Release completo -.\scripts\release.ps1 -DryRun # Teste sem publicar -.\scripts\release.ps1 -SkipTests # Pular testes -.\scripts\release.ps1 -DryRun -SkipTests # Teste rápido -``` - -**Funcionalidades**: -- ✅ Validação TypeScript -- ✅ ESLint check (aceita warnings) -- ✅ Testes (opcional com -SkipTests) -- ✅ Build do SDK -- ✅ Verificação de dist/ -- ✅ Criação de tarball -- ✅ Comandos git (opcional com -SkipGit) -- ✅ NPM publish (opcional com -DryRun) -- ✅ Resumo final colorido - -### Linux/macOS: `scripts/release.sh` - -```bash -# Primeira execução -chmod +x scripts/release.sh - -# Sintaxe -./scripts/release.sh [--dry-run] [--skip-tests] [--skip-git] - -# Exemplos -./scripts/release.sh # Release completo -./scripts/release.sh --dry-run # Teste sem publicar -./scripts/release.sh --skip-tests # Pular testes -./scripts/release.sh --dry-run --skip-tests # Teste rápido -``` - -**Funcionalidades**: -- ✅ Validação TypeScript -- ✅ ESLint check (aceita warnings) -- ✅ Testes (opcional com --skip-tests) -- ✅ Build do SDK -- ✅ Verificação de dist/ -- ✅ Criação de tarball -- ✅ Comandos git (opcional com --skip-git) -- ✅ NPM publish (opcional com --dry-run) -- ✅ Resumo final colorido (ANSI colors) - -## 🎨 Scripts Interativos - -### Windows: `RELEASE_COMMANDS.ps1` - -```powershell -# Executar -.\RELEASE_COMMANDS.ps1 -``` - -**Fluxo**: -1. **Validação** (automática) - - TypeScript check - - Build - - Verificação de package - -2. **Git Operations** (confirmação) - - Mostra comandos git - - Pergunta: "Executar comandos git agora? (y/N)" - - Se sim: executa add/commit/tag/push - - Se não: mostra comandos para executar manualmente - -3. **NPM Publish** (confirmação) - - Verifica login npm - - Executa dry-run - - Pergunta: "Continuar com publicação? (y/N)" - - Se sim: publica no NPM - - Se não: cancela - -4. **Pós-Release** - - Lista próximas ações manuais - - Links para GitHub Release - - Checklist de comunicação - -### Linux/macOS: `RELEASE_COMMANDS.sh` - -```bash -# Primeira execução -chmod +x RELEASE_COMMANDS.sh - -# Executar -./RELEASE_COMMANDS.sh -``` - -**Fluxo**: Idêntico ao PowerShell -- Mesmas 4 fases -- Mesmas confirmações interativas -- Output colorido ANSI -- Mesmas funcionalidades - -## 📋 Documentação de Suporte - -| Arquivo | Descrição | Tamanho | -|---------|-----------|---------| -| `README_RELEASE.md` | Guia completo de release (todas plataformas) | 6.1 KB | -| `RELEASE_CHECKLIST.md` | Checklist detalhado pré/pós-release | 4.6 KB | -| `CHANGELOG.md` | Release notes v3.0.0 | 5.4 KB | -| `MIGRATION.md` | Guia migração v2→v3 | 14.8 KB | - -## 🔄 Diferenças de Implementação - -### Cores no Terminal - -**PowerShell**: -```powershell -Write-Host "Mensagem" -ForegroundColor Green -``` - -**Bash**: -```bash -echo -e "${GREEN}Mensagem${NC}" -``` - -### Parâmetros - -**PowerShell**: -```powershell -param( - [switch]$DryRun = $false, - [switch]$SkipTests = $false -) -``` - -**Bash**: -```bash -for arg in "$@"; do - case $arg in - --dry-run) DRY_RUN=true ;; - --skip-tests) SKIP_TESTS=true ;; - esac -done -``` - -### Confirmações - -**PowerShell**: -```powershell -$confirm = Read-Host "Continuar? (y/N)" -if ($confirm -eq 'y' -or $confirm -eq 'Y') { - # Executar -} -``` - -**Bash**: -```bash -read -p "Continuar? (y/N) " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - # Executar -fi -``` - -## 🎯 Qual Script Usar? - -### Use Scripts Automatizados quando: -- ✅ Você quer release completo automático -- ✅ Precisa testar com dry-run -- ✅ Quer controle via parâmetros -- ✅ Execução em CI/CD -- ✅ Prefere não responder confirmações - -### Use Scripts Interativos quando: -- ✅ Primeira vez fazendo release -- ✅ Quer ver cada passo em detalhes -- ✅ Quer controle manual sobre git/npm -- ✅ Prefere confirmações antes de ações irreversíveis -- ✅ Aprendendo o processo - -## 🚀 Recomendação por Cenário - -### 1. Primeiro Release (Aprendizado) -```bash -# Windows -.\RELEASE_COMMANDS.ps1 - -# Linux/macOS -./RELEASE_COMMANDS.sh -``` -**Por quê?** Interativo, mostra cada passo, pede confirmação. - -### 2. Teste Rápido (CI/CD) -```bash -# Windows -.\scripts\release.ps1 -DryRun -SkipTests - -# Linux/macOS -./scripts/release.sh --dry-run --skip-tests -``` -**Por quê?** Rápido, sem testes, sem publicação real. - -### 3. Release de Produção (Confiante) -```bash -# Windows -.\scripts\release.ps1 - -# Linux/macOS -./scripts/release.sh -``` -**Por quê?** Completo, com todas validações, publica no NPM. - -### 4. Apenas Validação (Sem Git/NPM) -```bash -# Windows -.\scripts\release.ps1 -SkipGit -DryRun - -# Linux/macOS -./scripts/release.sh --skip-git --dry-run -``` -**Por quê?** Valida código mas não mexe em git nem NPM. - -## 📝 Checklist de Uso - -Antes de executar qualquer script: - -- [ ] `npm run typecheck` passou -- [ ] `npm run build` gerou dist/ -- [ ] README.md é versão v3 -- [ ] package.json version = 3.0.0 -- [ ] Logado no NPM (`npm whoami`) -- [ ] Git configurado e permissões OK - -## 🆘 Troubleshooting Específico - -### PowerShell: "Execution policy error" -```powershell -# Solução temporária -Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass - -# Ou execute com: -powershell -ExecutionPolicy Bypass -File .\scripts\release.ps1 -``` - -### Bash: "Permission denied" -```bash -# Dar permissão de execução -chmod +x scripts/release.sh -chmod +x RELEASE_COMMANDS.sh -``` - -### Bash: "command not found: npm" -```bash -# Verificar PATH -echo $PATH - -# Ou usar caminho completo -/usr/local/bin/npm run build -``` - -## 🎉 Conclusão - -Ambas as implementações (PowerShell e Bash) são **totalmente equivalentes** em funcionalidade. A escolha depende apenas do sistema operacional: - -- **Windows** → Use `.ps1` scripts -- **Linux/macOS** → Use `.sh` scripts -- **WSL no Windows** → Pode usar ambos! - -Todos os scripts foram testados e estão prontos para uso em produção! 🚀 diff --git a/package.json b/package.json index 3000ae5..bc11574 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "dist", "README.md", "CHANGELOG.md", - "MIGRATION.md" + "MIGRATION.md", + "skills" ], "scripts": { "dev": "tsx watch src/index.ts", @@ -88,5 +89,14 @@ "vitest": "^1.0.0", "yaml": "^2.3.4", "dotenv": "~17.2.3" + }, + "agents": { + "skills": [ + { + "name": "nfe-io-sdk", + "path": "./skills/nfe-io-sdk", + "description": "A Node.js SDK for interacting with the NFE.io API, providing functionalities to manage electronic invoices (NF-e) and service invoices (NFS-e) in Brazil." + } + ] } } diff --git a/skills/nfeio-sdk/SKILL.md b/skills/nfeio-sdk/SKILL.md new file mode 100644 index 0000000..993c67d --- /dev/null +++ b/skills/nfeio-sdk/SKILL.md @@ -0,0 +1,353 @@ +--- +name: nfeio-sdk +description: "NFE.io Node.js SDK integration expert (package: nfe-io). MUST trigger when: code imports 'nfe-io' or references NfeClient; user mentions NFE.io, NFS-e, NF-e, CT-e, CF-e, CFe-SAT, nota fiscal, nota fiscal eletronica, Brazilian invoice, fiscal document, electronic invoice Brazil, CNPJ lookup, CPF lookup, service invoice, product invoice, transportation invoice, consumer invoice, tax calculation Brazilian taxes, SEFAZ, NFE API, emissao de nota, consulta CNPJ, consulta CPF, consulta CEP; user works with Brazilian electronic fiscal documents, tax documents, or Brazilian tax compliance; any file contains 'nfe-io' in package.json or import statements; user mentions polling for invoice status, invoice async processing, or certificate management for Brazilian fiscal documents. Covers all 16 SDK resources, async invoice processing with polling, discriminated union responses, error hierarchy, certificate management, webhook signature validation, address lookup (CEP), legal entity and natural person lookups, tax calculation engine, and pagination patterns. Use this skill even if the user doesn't explicitly name the SDK -- if they're building anything related to Brazilian fiscal document automation in Node.js/TypeScript, this skill applies." +--- + +# NFE.io Node.js SDK Integration Guide + +This skill enables you to write correct, production-ready code using the NFE.io SDK for Brazilian electronic fiscal documents. The SDK covers NFS-e (service invoices), NF-e (product invoices), CT-e (transportation), CFe-SAT (consumer), CNPJ/CPF lookups, tax calculation, and more. + +## Package & Import + +The npm package name is **`nfe-io`** (not `@nfe-io/sdk` despite what JSDoc comments say). + +```typescript +// ESM (recommended) +import { NfeClient } from 'nfe-io'; + +// CommonJS +const { NfeClient } = require('nfe-io'); + +// Default export (factory function) +import nfeFactory from 'nfe-io'; +const nfe = nfeFactory({ apiKey: 'your-key' }); +``` + +Requirements: Node.js 18+ (uses native `fetch`). Zero runtime dependencies. + +## Quick Start + +```typescript +import { NfeClient } from 'nfe-io'; + +const nfe = new NfeClient({ + apiKey: 'your-api-key', // Required for most resources + environment: 'production', // 'production' | 'development' + timeout: 30000, // Request timeout in ms (default: 30000) + retryConfig: { // Optional retry configuration + maxRetries: 3, // Default: 3 + baseDelay: 1000, // Default: 1000ms + maxDelay: 30000, // Default: 30000ms + backoffMultiplier: 2, // Default: 2 + }, +}); +``` + +**Dual API keys**: Some data-service resources (addresses, CNPJ/CPF lookups, tax calculation) can use a separate `dataApiKey`. If not set, they fall back to `apiKey`. + +```typescript +const nfe = new NfeClient({ + apiKey: process.env.NFE_API_KEY, + dataApiKey: process.env.NFE_DATA_API_KEY, // Optional, falls back to apiKey +}); +``` + +**From environment variable**: +```typescript +import { createClientFromEnv } from 'nfe-io'; +// Reads NFE_API_KEY env var +const nfe = createClientFromEnv('production'); +``` + +## Resource Map + +All resources are lazy-initialized via property getters on `NfeClient`. No resource is instantiated until first access. + +| Accessor | Resource | API Host | Scope | Key Operations | +|----------|----------|----------|-------|----------------| +| `nfe.serviceInvoices` | NFS-e Service Invoices | api.nfe.io | Company | create, createAndWait, list, retrieve, cancel, sendEmail, downloadPdf/Xml | +| `nfe.companies` | Companies | api.nfe.io | Global | CRUD, uploadCertificate, findByTaxNumber, listAll, listIterator | +| `nfe.legalPeople` | Legal People (PJ) | api.nfe.io | Company | CRUD, createBatch, findByTaxNumber | +| `nfe.naturalPeople` | Natural People (PF) | api.nfe.io | Company | CRUD, createBatch, findByTaxNumber | +| `nfe.webhooks` | Webhooks | api.nfe.io | Company | CRUD, validateSignature, test | +| `nfe.addresses` | Address Lookup | address.api.nfe.io | Global | lookupByPostalCode, search, lookupByTerm | +| `nfe.productInvoices` | NF-e Product Invoices | api.nfse.io | Company | create, list, retrieve, cancel, downloadPdf/Xml, sendCorrectionLetter | +| `nfe.stateTaxes` | State Tax (IE) | api.nfse.io | Company | CRUD (prerequisite for NF-e issuance) | +| `nfe.taxCalculation` | Tax Engine | api.nfse.io | Tenant | calculate (ICMS, PIS, COFINS, IPI, II) | +| `nfe.taxCodes` | Tax Code Reference | api.nfse.io | Global | listOperationCodes, listAcquisitionPurposes, listIssuer/RecipientTaxProfiles | +| `nfe.transportationInvoices` | CT-e Transport | api.nfse.io | Company | enable/disable, retrieve, downloadXml | +| `nfe.inboundProductInvoices` | Inbound NF-e | api.nfse.io | Company | enableAutoFetch, getDetails, downloadXml/Pdf, manifest | +| `nfe.productInvoiceQuery` | NF-e Query (SEFAZ) | nfe.api.nfe.io | Global | retrieve, downloadPdf/Xml, listEvents | +| `nfe.consumerInvoiceQuery` | CFe-SAT Query | nfe.api.nfe.io | Global | retrieve, downloadXml | +| `nfe.legalEntityLookup` | CNPJ Lookup | legalentity.api.nfe.io | Global | getBasicInfo, getStateTaxInfo, getStateTaxForInvoice | +| `nfe.naturalPersonLookup` | CPF Lookup | naturalperson.api.nfe.io | Global | getStatus | + +**Company-scoped** resources require `companyId` as the first parameter in every method. **Global** resources operate without company context. + +## Core Pattern: Company-Scoped Operations + +Most resources are scoped to a company. The pattern is always `resource.method(companyId, ...)`: + +```typescript +// List service invoices for a company +const invoices = await nfe.serviceInvoices.list('company-uuid', { + pageIndex: 0, + pageCount: 50, +}); + +// Create a legal person under a company +const person = await nfe.legalPeople.create('company-uuid', { + federalTaxNumber: '11444555000149', // String for people resources + name: 'Example Company Ltda', + email: 'contact@example.com', +}); +``` + +## Core Pattern: Async Invoice Processing (Critical) + +Service invoice creation can return either an immediate result (201) or an async pending result (202), depending on the municipality. The SDK uses a **discriminated union** return type: + +```typescript +// WRONG: assuming create() returns an invoice directly +const invoice = await nfe.serviceInvoices.create(companyId, data); // This is a union type! + +// CORRECT: handling both cases explicitly +const result = await nfe.serviceInvoices.create(companyId, data); +if (result.status === 'immediate') { + console.log('Invoice issued:', result.invoice.id); +} else if (result.status === 'async') { + console.log('Processing...', result.response.invoiceId); + // Need to poll for completion +} +``` + +**Recommended: Use `createAndWait()`** — handles polling automatically: + +```typescript +const invoice = await nfe.serviceInvoices.createAndWait(companyId, { + cityServiceCode: '2690', + description: 'IT Consulting Services', + servicesAmount: 1500.00, + borrower: { + federalTaxNumber: 11444555000149, + name: 'Client Company', + email: 'client@example.com', + }, +}, { + timeout: 300000, // 5 min (some municipalities are slow) + initialDelay: 1000, // 1s before first poll + maxDelay: 10000, // Max 10s between polls + backoffFactor: 1.5, // Exponential backoff + onPoll: (attempt, flowStatus) => { + console.log(`Poll #${attempt}: ${flowStatus}`); + }, +}); + +console.log('Invoice status:', invoice.flowStatus); // 'Issued' | 'IssueFailed' +``` + +**Terminal FlowStatus values**: `Issued`, `IssueFailed`, `Cancelled`, `CancelFailed` +**Non-terminal** (still processing): `WaitingSend`, `WaitingReturn`, `WaitingDownload`, `WaitingCalculateTaxes`, `WaitingDefineRpsNumber`, `WaitingSendCancel`, `PullFromCityHall` + +## Core Pattern: Error Handling + +The SDK provides a rich error hierarchy. Every error extends `NfeError`: + +| Error Class | HTTP Status | When Thrown | +|------------|-------------|-------------| +| `AuthenticationError` | 401 | Invalid or missing API key | +| `ValidationError` | 400/422 | Invalid request data | +| `NotFoundError` | 404 | Resource doesn't exist | +| `ConflictError` | 409 | Duplicate or conflict | +| `RateLimitError` | 429 | Too many requests | +| `ServerError` | 5xx | Server-side failures | +| `ConnectionError` | — | Network/DNS failures | +| `TimeoutError` | — | Request timeout | +| `ConfigurationError` | — | Invalid SDK configuration | +| `PollingTimeoutError` | — | Polling exceeded timeout | +| `InvoiceProcessingError` | — | Invoice processing failed | + +```typescript +import { + NfeError, AuthenticationError, ValidationError, + NotFoundError, RateLimitError, TimeoutError, + PollingTimeoutError, isNfeError, isValidationError, +} from 'nfe-io'; + +try { + const invoice = await nfe.serviceInvoices.createAndWait(companyId, data); +} catch (error) { + if (error instanceof AuthenticationError) { + console.error('Check your API key'); + } else if (error instanceof ValidationError) { + console.error('Invalid data:', error.details); + } else if (error instanceof PollingTimeoutError) { + console.error('Invoice still processing after timeout'); + } else if (isNfeError(error)) { + console.error(`${error.type} [${error.statusCode}]: ${error.message}`); + console.error('Details:', error.details); + } +} +``` + +All error objects have: `message`, `type`, `code`/`status`/`statusCode`, `details`, `raw`, `toJSON()`. + +## Core Pattern: Pagination + +The SDK uses **two different pagination styles**: + +**Offset-based** (service invoices, companies, people, webhooks): +```typescript +const page = await nfe.serviceInvoices.list(companyId, { + pageIndex: 0, // 0-based page number + pageCount: 50, // Items per page +}); +// page.data: ServiceInvoiceData[] +// page.totalCount: number +``` + +**Cursor-based** (product invoices, state taxes): +```typescript +const page = await nfe.productInvoices.list(companyId, { + environment: 'Production', // REQUIRED for product invoices + limit: 25, + startingAfter: 'last-invoice-id', // Cursor for next page +}); +``` + +**Auto-pagination helpers** (companies only): +```typescript +const allCompanies = await nfe.companies.listAll(); + +// Or async iterator +for await (const company of nfe.companies.listIterator()) { + console.log(company.name); +} +``` + +## Core Pattern: File Downloads + +PDF and XML downloads return `Buffer` objects for most resources: + +```typescript +import { writeFileSync } from 'node:fs'; + +// Service invoice downloads return Buffer +const pdf = await nfe.serviceInvoices.downloadPdf(companyId, invoiceId); +const xml = await nfe.serviceInvoices.downloadXml(companyId, invoiceId); +writeFileSync('invoice.pdf', pdf); +writeFileSync('invoice.xml', xml); + +// Query resources also return Buffer +const danfe = await nfe.productInvoiceQuery.downloadPdf(accessKey); +``` + +**Exception**: `productInvoices.downloadPdf()` returns `NfeFileResource` (with a `uri` field), not a raw Buffer. Use the URI to fetch the file separately. + +## Core Pattern: Certificates + +Digital certificates (A1 PFX/P12) are required for NF-e issuance: + +```typescript +import { readFileSync } from 'node:fs'; +import { CertificateValidator } from 'nfe-io'; + +const certBuffer = readFileSync('certificate.pfx'); + +// Validate before uploading +const validation = await CertificateValidator.validate(certBuffer, 'password'); +if (validation.valid) { + await nfe.companies.uploadCertificate(companyId, certBuffer, 'password'); +} + +// Check certificate status +const status = await nfe.companies.getCertificateStatus(companyId); +console.log('Expires:', status.expiresOn); + +// Find companies with expiring certificates +const expiring = await nfe.companies.getCompaniesWithExpiringCertificates(30); // 30 days +``` + +## Core Pattern: Webhooks + +```typescript +// Create webhook +const webhook = await nfe.webhooks.create(companyId, { + url: 'https://your-app.com/webhooks/nfe', + events: ['invoice.created', 'invoice.issued', 'invoice.cancelled', 'invoice.failed'], + active: true, +}); + +// Validate incoming webhook signature (in your handler) +const isValid = nfe.webhooks.validateSignature( + rawBody, // Request body as string + signature, // X-Hub-Signature header value + webhook.secret!, // Secret from webhook creation +); +``` + +Available events: `invoice.created`, `invoice.issued`, `invoice.cancelled`, `invoice.failed`. + +## Critical Pitfalls + +1. **Import path**: Use `'nfe-io'` not `'@nfe-io/sdk'`. The JSDoc uses `@nfe-io/sdk` but the npm package is `nfe-io`. + +2. **`federalTaxNumber` type varies**: For `companies.create()` it's a **number** (`12345678000190`). For `legalPeople`/`naturalPeople` and lookup resources it's a **string** (`'12345678000190'`). + +3. **`serviceInvoices.create()` returns a union**: The return type is `{ status: 'immediate', invoice } | { status: 'async', response }`. Always use `createAndWait()` unless you need manual control. + +4. **Product invoices have NO `createAndWait()`**: `productInvoices.create()` is always async (returns 202). Completion is notified via **webhooks only**. Do not try to poll. + +5. **`productInvoices.list()` requires `environment`**: You must pass `environment: 'Production'` or `environment: 'Test'`. Omitting it throws `ValidationError`. + +6. **Method is `companies.remove()`** not `companies.delete()` — avoids JS reserved keyword conflict. + +7. **Data services need API key**: Resources on non-main hosts (addresses, lookups, tax calculation) use `dataApiKey` with `apiKey` as fallback. Ensure at least one is set. + +8. **Different pagination**: Service invoices use offset (`pageIndex`/`pageCount`), product invoices use cursor (`startingAfter`/`endingBefore`/`limit`). + +9. **Polling timeout**: Default is 2 minutes. Some municipalities take 3-5 minutes. Set `timeout: 300000` for production use. + +10. **Access keys**: Must be exactly 44 numeric digits. Used for query resources and inbound documents. + +11. **Correction letters**: `sendCorrectionLetter()` text must be 15-1000 characters, no accents or special characters. + +12. **Product invoice PDF**: `productInvoices.downloadPdf()` returns `NfeFileResource` (object with `uri`), not a `Buffer`. Use `productInvoiceQuery.downloadPdf(accessKey)` for a raw Buffer. + +## Decision Tree: "I want to..." + +| Goal | Resource & Method | +|------|-------------------| +| Issue a service invoice (NFS-e) | `nfe.serviceInvoices.createAndWait(companyId, data)` | +| Issue a product invoice (NF-e) | `nfe.productInvoices.create(companyId, data)` + webhook | +| Issue NF-e with specific state tax | `nfe.productInvoices.createWithStateTax(companyId, stateTaxId, data)` | +| Query existing NF-e by access key | `nfe.productInvoiceQuery.retrieve(accessKey)` | +| Query CFe-SAT coupon by access key | `nfe.consumerInvoiceQuery.retrieve(accessKey)` | +| Receive inbound NF-e automatically | `nfe.inboundProductInvoices.enableAutoFetch(companyId)` | +| Receive inbound CT-e automatically | `nfe.transportationInvoices.enable(companyId)` | +| Look up CNPJ (company info) | `nfe.legalEntityLookup.getBasicInfo(cnpj)` | +| Look up CPF (person status) | `nfe.naturalPersonLookup.getStatus(cpf, birthDate)` | +| Look up address by CEP | `nfe.addresses.lookupByPostalCode('01310-100')` | +| Calculate Brazilian taxes | `nfe.taxCalculation.calculate(tenantId, request)` | +| Manage companies & certificates | `nfe.companies.*` | +| Manage people (PJ) under company | `nfe.legalPeople.*` | +| Manage people (PF) under company | `nfe.naturalPeople.*` | +| Set up webhook notifications | `nfe.webhooks.create(companyId, {...})` | +| Manage state tax registrations (IE) | `nfe.stateTaxes.*` | +| Cancel a service invoice | `nfe.serviceInvoices.cancel(companyId, invoiceId)` | +| Cancel a product invoice | `nfe.productInvoices.cancel(companyId, invoiceId)` | +| Download DANFE PDF | `nfe.serviceInvoices.downloadPdf(companyId, id)` or `nfe.productInvoiceQuery.downloadPdf(accessKey)` | +| Send invoice by email | `nfe.serviceInvoices.sendEmail(companyId, invoiceId)` | +| Issue correction letter (CC-e) | `nfe.productInvoices.sendCorrectionLetter(companyId, id, data)` | + +## Reference Files + +Load these when you need detailed method signatures, full type definitions, or specific implementation guidance: + +- **`references/service-invoices-and-polling.md`** — Read when working with NFS-e service invoices, companies, people (PJ/PF), webhooks, or polling. Contains complete method signatures, PollingOptions, CreateInvoiceResponse union, FlowStatus states, and certificate management details. + +- **`references/product-invoices-and-taxes.md`** — Read when working with NF-e product invoices, state tax registrations (IE), tax calculation, tax codes, CT-e, or inbound NF-e distribution. Contains cursor pagination, NfeProductInvoiceIssueData structure, TaxCalculation request/response, and manifest events. + +- **`references/data-services-and-lookups.md`** — Read when working with CNPJ/CPF lookups, address/CEP lookup, NF-e/CFe-SAT query by access key. Contains LegalEntityBasicInfo structure, BrazilianState codes, AddressLookupResponse, and host mapping. + +- **`references/error-handling-and-patterns.md`** — Read when implementing error handling, retry strategies, or debugging SDK issues. Contains complete error class hierarchy, type guards, ErrorFactory, RetryConfig, and CertificateValidator. diff --git a/skills/nfeio-sdk/references/data-services-and-lookups.md b/skills/nfeio-sdk/references/data-services-and-lookups.md new file mode 100644 index 0000000..6fb3426 --- /dev/null +++ b/skills/nfeio-sdk/references/data-services-and-lookups.md @@ -0,0 +1,313 @@ +# Data Services & Lookups + +Detailed reference for read-only query and lookup resources across multiple API hosts. + +## API Host Mapping + +| Resource | API Host | Auth Key | +|----------|----------|----------| +| Addresses | `address.api.nfe.io/v2` | `dataApiKey` (fallback: `apiKey`) | +| ProductInvoiceQuery | `nfe.api.nfe.io` | `apiKey` | +| ConsumerInvoiceQuery | `nfe.api.nfe.io` | `apiKey` | +| LegalEntityLookup | `legalentity.api.nfe.io` | `dataApiKey` (fallback: `apiKey`) | +| NaturalPersonLookup | `naturalperson.api.nfe.io` | `dataApiKey` (fallback: `apiKey`) | + +Resources using `dataApiKey` will fall back to `apiKey` if `dataApiKey` is not configured. At least one must be set. + +--- + +## AddressesResource + +Access via `nfe.addresses`. Global scope (no company ID needed). + +### lookupByPostalCode(postalCode): Promise + +```typescript +// Both formats accepted: +const result = await nfe.addresses.lookupByPostalCode('01310-100'); +const result = await nfe.addresses.lookupByPostalCode('01310100'); +``` + +Validates CEP format (8 digits, with or without dash). + +### search(options?): Promise + +OData filter expression support: +```typescript +const result = await nfe.addresses.search({ + filter: "city eq 'Sao Paulo' and state eq 'SP'", +}); +``` + +### lookupByTerm(term): Promise + +Free-text search: +```typescript +const result = await nfe.addresses.lookupByTerm('Avenida Paulista Sao Paulo'); +``` + +### AddressLookupResponse + +```typescript +interface AddressLookupResponse { + addresses: Address[]; +} + +interface Address { + state: string; // UF code (e.g., 'SP') + city: { + code: string; // IBGE city code + name: string; + }; + district: string; + street: string; + streetSuffix: string; + number: string; + numberMin: string; + numberMax: string; + additionalInformation: string; + postalCode: string; // Format: '01310100' (no dash) + country: string; +} +``` + +--- + +## ProductInvoiceQueryResource (NF-e SEFAZ Query) + +Access via `nfe.productInvoiceQuery`. Global scope. +Queries NF-e data directly from SEFAZ by access key. No company scope needed. + +### retrieve(accessKey): Promise + +```typescript +// Access key must be exactly 44 numeric digits +const invoice = await nfe.productInvoiceQuery.retrieve('35210512345678000190550010000001231123456789'); +``` + +**ProductInvoiceDetails** contains: +- `issuer`: Company that issued (CNPJ, name, address, IE) +- `buyer`: Recipient (CNPJ/CPF, name, address) +- `items[]`: Line items with description, NCM, CFOP, quantity, amounts, taxes +- `totals`: ICMS totals, ISSQN totals +- `transport`: Shipping information +- `payment`: Payment details +- `protocol`: SEFAZ authorization protocol +- `additionalInfo`: Fisco/taxpayer notes +- `events[]`: Fiscal events (cancellation, correction, manifestation) + +Key status values: +```typescript +type ProductInvoiceStatus = 'unknown' | 'authorized' | 'canceled'; +type ProductInvoiceOperationType = 'incoming' | 'outgoing'; +type ProductInvoiceEnvironmentType = 'production' | 'test'; +``` + +### downloadPdf(accessKey): Promise + +Downloads DANFE PDF as raw `Buffer`. Write directly to file: +```typescript +const pdf = await nfe.productInvoiceQuery.downloadPdf(accessKey); +writeFileSync('danfe.pdf', pdf); +``` + +### downloadXml(accessKey): Promise + +Downloads NF-e XML as raw `Buffer`. + +### listEvents(accessKey): Promise + +Lists fiscal events for the invoice (cancellations, correction letters, manifestations). + +--- + +## ConsumerInvoiceQueryResource (CFe-SAT) + +Access via `nfe.consumerInvoiceQuery`. Global scope. +Queries CFe-SAT (Cupom Fiscal Eletronico) data from SEFAZ. + +### retrieve(accessKey): Promise + +```typescript +const coupon = await nfe.consumerInvoiceQuery.retrieve(accessKey); +``` + +**TaxCoupon** contains: +- `issuer`: Emitter details +- `buyer`: Consumer details (optional, may be anonymous) +- `items[]`: Line items with description, NCM, CFOP, quantity, taxes +- `total`: ICMS and ISSQN totals +- `payment`: Payment details (cash, card, etc.) +- `additionalInformation`: Extra data + +Key status values: +```typescript +type CouponStatus = 'Unknown' | 'Authorized' | 'Canceled'; +type CouponTaxRegime = 'National_Simple' | 'National_Simple_Brute' | 'Normal_Regime'; +``` + +### downloadXml(accessKey): Promise + +Downloads CFe XML as raw `Buffer`. + +--- + +## LegalEntityLookupResource (CNPJ Query) + +Access via `nfe.legalEntityLookup`. Global scope. +Queries Receita Federal for company data by CNPJ. + +### getBasicInfo(cnpj, options?): Promise + +```typescript +// CNPJ accepts formatted or raw string +const info = await nfe.legalEntityLookup.getBasicInfo('12.345.678/0001-90'); +const info = await nfe.legalEntityLookup.getBasicInfo('12345678000190'); + +interface LegalEntityBasicInfoOptions { + includePartners?: boolean; // Include partner/shareholder info + includeActivities?: boolean; // Include CNAE economic activities +} +``` + +**LegalEntityBasicInfo** key fields: +```typescript +{ + federalTaxNumber: string; // CNPJ + name: string; // Legal name (razao social) + tradeName?: string; // Trade name (nome fantasia) + status: LegalEntityStatus; // 'Active' | 'Suspended' | 'Cancelled' | ... + size: LegalEntitySize; // 'ME' | 'EPP' | 'DEMAIS' + unit: LegalEntityUnit; // 'Headoffice' | 'Subsidiary' + natureCode: LegalEntityNatureCode; + taxRegime: LegalEntityTaxRegime; // 'SimplesNacional' | 'MEI' | 'Normal' | 'Unknown' + openingDate?: string; + email?: string; + capital?: number; // Social capital + address?: LegalEntityAddress; + phones?: LegalEntityPhone[]; + activities?: LegalEntityEconomicActivity[]; // CNAE codes + partners?: LegalEntityPartner[]; +} +``` + +Status enum: +```typescript +type LegalEntityStatus = 'Unknown' | 'Active' | 'Suspended' | 'Cancelled' | 'Unabled' | 'Null'; +``` + +### getStateTaxInfo(state, cnpj): Promise + +Get state tax registrations (Inscricoes Estaduais) for a specific state: + +```typescript +const taxInfo = await nfe.legalEntityLookup.getStateTaxInfo('SP', '12345678000190'); +``` + +**BrazilianState** type — all 27 UF codes plus special values: +```typescript +type BrazilianState = + | 'AC' | 'AL' | 'AM' | 'AP' | 'BA' | 'CE' | 'DF' | 'ES' + | 'GO' | 'MA' | 'MG' | 'MS' | 'MT' | 'PA' | 'PB' | 'PE' + | 'PI' | 'PR' | 'RJ' | 'RN' | 'RO' | 'RR' | 'RS' | 'SC' + | 'SE' | 'SP' | 'TO' + | 'EX' // Exterior (foreign) + | 'NA'; // Not applicable +``` + +Returns state tax registrations with status per state: +```typescript +interface LegalEntityStateTax { + taxNumber: string; // IE number + state: BrazilianState; + status: LegalEntityStateTaxStatus; // 'Abled' | 'Unabled' | 'Cancelled' | 'Unknown' +} +``` + +### getStateTaxForInvoice(state, cnpj): Promise + +Like `getStateTaxInfo()` but with extended status evaluation for invoice issuance. Use this to determine which IE to use when creating product invoices. + +### getSuggestedStateTaxForInvoice(state, cnpj): Promise + +Auto-detects the best IE for invoice issuance. Enterprise use case. + +--- + +## NaturalPersonLookupResource (CPF Query) + +Access via `nfe.naturalPersonLookup`. Global scope. +Queries Receita Federal for CPF cadastral status. + +### getStatus(cpf, birthDate): Promise + +```typescript +// CPF accepts formatted or raw string +// birthDate as 'YYYY-MM-DD' string +const status = await nfe.naturalPersonLookup.getStatus( + '123.456.789-09', + '1990-01-15' +); +``` + +**NaturalPersonStatusResponse**: +```typescript +{ + name: string; + status: NaturalPersonStatus; + registrationDate?: string; + // ... +} + +type NaturalPersonStatus = + | 'Regular' + | 'Suspensa' + | 'Cancelada' + | 'Titular Falecido' + | 'Pendente de Regularizacao' + | 'Nula'; +``` + +--- + +## Common Patterns + +### Address from CEP to Invoice + +```typescript +// 1. Look up address by CEP +const { addresses } = await nfe.addresses.lookupByPostalCode('01310-100'); +const addr = addresses[0]; + +// 2. Use in invoice borrower +await nfe.serviceInvoices.createAndWait(companyId, { + // ... + borrower: { + federalTaxNumber: 12345678000190, + name: 'Client', + address: { + country: addr.country, + postalCode: addr.postalCode, + street: addr.street, + number: '100', + district: addr.district, + city: { code: addr.city.code, name: addr.city.name }, + state: addr.state, + }, + }, +}); +``` + +### CNPJ Validation Before Invoice + +```typescript +// 1. Check if CNPJ is active +const { legalEntity } = await nfe.legalEntityLookup.getBasicInfo(cnpj); +if (legalEntity?.status !== 'Active') { + throw new Error(`CNPJ ${cnpj} is not active: ${legalEntity?.status}`); +} + +// 2. Check state tax for NF-e +const { legalEntity: taxInfo } = await nfe.legalEntityLookup.getStateTaxForInvoice('SP', cnpj); +const validIE = taxInfo?.stateTaxes?.find(t => t.status === 'Abled'); +``` diff --git a/skills/nfeio-sdk/references/error-handling-and-patterns.md b/skills/nfeio-sdk/references/error-handling-and-patterns.md new file mode 100644 index 0000000..6ee9351 --- /dev/null +++ b/skills/nfeio-sdk/references/error-handling-and-patterns.md @@ -0,0 +1,328 @@ +# Error Handling & Cross-Cutting Patterns + +Detailed reference for error classes, retry logic, polling, and certificate validation. + +## Error Class Hierarchy + +All errors extend the base `NfeError` class: + +``` +NfeError (base) + HTTP errors (from API responses): + AuthenticationError (401) + ValidationError (400, 422) + NotFoundError (404) + ConflictError (409) + RateLimitError (429) + ServerError (500, 502, 503, 504) + Network errors: + ConnectionError (DNS, network failures) + TimeoutError (AbortController timeout) + SDK errors: + ConfigurationError (invalid SDK config) + PollingTimeoutError (polling exceeded timeout) + InvoiceProcessingError (invoice reached failed state) +``` + +### NfeError (Base Class) + +```typescript +class NfeError extends Error { + readonly type: string; // Error class name (e.g., 'ValidationError') + readonly code?: number; // HTTP status code + readonly status?: number; // Alias for code + readonly details?: unknown; // API error details (often has message, errors array) + readonly raw?: unknown; // Raw response body + + get statusCode(): number | undefined; // Getter alias for code + toJSON(): object; // Serializable representation +} +``` + +### Error Properties by Type + +| Error | Status | When | Key Details | +|-------|--------|------|-------------| +| `AuthenticationError` | 401 | Invalid/missing API key | Check `apiKey` config | +| `ValidationError` | 400/422 | Bad request data | `details` has field-level errors | +| `NotFoundError` | 404 | Resource doesn't exist | Check ID/access key | +| `ConflictError` | 409 | Duplicate resource | Existing resource info in `details` | +| `RateLimitError` | 429 | Too many requests | Auto-retried by HTTP client | +| `ServerError` | 5xx | Server failures | Auto-retried by HTTP client | +| `ConnectionError` | - | Network/DNS failure | Auto-retried by HTTP client | +| `TimeoutError` | - | Request exceeded timeout | Increase `timeout` config | +| `ConfigurationError` | - | Bad SDK config | Missing apiKey, bad env, etc. | +| `PollingTimeoutError` | - | Polling exceeded timeout | Increase polling `timeout` option | +| `InvoiceProcessingError` | - | Invoice failed processing | `details.flowStatus`, `details.flowMessage` | + +## Type Guard Functions + +Use type guards for runtime checking without `instanceof`: + +```typescript +import { + isNfeError, + isAuthenticationError, + isValidationError, + isNotFoundError, + isConnectionError, + isTimeoutError, + isPollingTimeoutError, +} from 'nfe-io'; + +try { + await nfe.serviceInvoices.create(companyId, data); +} catch (error) { + if (isValidationError(error)) { + // error is typed as ValidationError + console.error('Fields:', error.details); + } else if (isNfeError(error)) { + // error is typed as NfeError (any SDK error) + console.error(`[${error.type}] ${error.message}`); + } +} +``` + +## ErrorFactory + +Create errors programmatically (useful for testing or custom middleware): + +```typescript +import { ErrorFactory } from 'nfe-io'; + +// From HTTP response +const error = ErrorFactory.fromHttpResponse(404, { message: 'Not found' }); +// Returns NotFoundError instance + +// From network error +const error = ErrorFactory.fromNetworkError(new TypeError('fetch failed')); +// Returns ConnectionError instance + +// From missing API key +const error = ErrorFactory.fromMissingApiKey(); +// Returns ConfigurationError instance +``` + +## Legacy Aliases (v2 Compatibility) + +```typescript +import { BadRequestError, APIError, InternalServerError } from 'nfe-io'; + +// BadRequestError === ValidationError +// APIError === NfeError +// InternalServerError === ServerError +``` + +--- + +## Retry Configuration + +The HTTP client automatically retries on transient failures. + +### Default Configuration + +```typescript +{ + maxRetries: 3, + baseDelay: 1000, // 1 second + maxDelay: 30000, // 30 seconds + backoffMultiplier: 2, // Exponential backoff +} +``` + +### Retry Behavior + +| Condition | Retried? | +|-----------|----------| +| 5xx server errors | Yes | +| 429 rate limit | Yes | +| Network errors | Yes | +| Request timeout | Yes | +| 4xx client errors (except 429) | No | +| `AuthenticationError` (401) | No | +| `ValidationError` (400) | No | +| `NotFoundError` (404) | No | + +### Backoff Formula + +``` +delay = min(baseDelay * (multiplier ^ attempt) + jitter, maxDelay) +``` + +Jitter is ~10% random variation to prevent thundering herd. + +### Custom Retry Config + +```typescript +const nfe = new NfeClient({ + apiKey: 'xxx', + retryConfig: { + maxRetries: 5, // More retries for unreliable networks + baseDelay: 2000, // Start slower + maxDelay: 60000, // Allow up to 1 minute between retries + backoffMultiplier: 1.5, // Gentler backoff + }, +}); +``` + +--- + +## Polling Utility + +The internal polling utility powers `createAndWait()` and `pollUntilComplete()`. + +### Generic Poll Function + +```typescript +import type { PollingOptions } from 'nfe-io'; + +interface PollingOptions { + fn: () => Promise; // Function to call repeatedly + isComplete: (result: T) => boolean; // Completion checker + timeout?: number; // Total timeout (default: 120000ms) + initialDelay?: number; // First delay (default: 1000ms) + maxDelay?: number; // Max between polls (default: 10000ms) + backoffFactor?: number; // Exponential (default: 1.5) + onPoll?: (attempt: number, result: T) => void; + onError?: (error: Error, attempt: number) => boolean; // Return false to stop +} +``` + +### NfeClient.pollUntilComplete() + +Direct access to polling on the client: + +```typescript +const result = await nfe.pollUntilComplete( + locationUrl, // URL from 202 response Location header + { + maxAttempts: 30, // Default: 30 + intervalMs: 2000, // Default: 2000ms + } +); +``` + +### Polling Best Practices + +1. **Set adequate timeout**: Default 2 minutes is often insufficient. Use 5 minutes (`timeout: 300000`) for production. +2. **Use `onPoll` callback**: Log progress for debugging and monitoring. +3. **Handle `PollingTimeoutError`**: The invoice may still be processing. Store the ID and check later. +4. **Prefer `createAndWait()`**: Over manual create + poll. Handles edge cases (ID extraction, status checking). + +--- + +## CertificateValidator + +Standalone utility for validating A1 digital certificates (PFX/P12 format). + +```typescript +import { CertificateValidator } from 'nfe-io'; +import { readFileSync } from 'node:fs'; + +const certBuffer = readFileSync('certificate.pfx'); + +// Full validation +const result = await CertificateValidator.validate(certBuffer, 'password'); +if (result.valid) { + console.log('Subject:', result.metadata?.subject); + console.log('Issuer:', result.metadata?.issuer); + console.log('Valid from:', result.metadata?.validFrom); + console.log('Valid to:', result.metadata?.validTo); +} else { + console.error('Invalid:', result.error); +} +``` + +--- + +## Common Error Handling Patterns + +### Full Error Handler + +```typescript +import { + AuthenticationError, + ValidationError, + NotFoundError, + RateLimitError, + TimeoutError, + PollingTimeoutError, + InvoiceProcessingError, + NfeError, +} from 'nfe-io'; + +async function safeCreateInvoice(companyId: string, data: any) { + try { + return await nfe.serviceInvoices.createAndWait(companyId, data, { + timeout: 300000, + }); + } catch (error) { + if (error instanceof AuthenticationError) { + // API key is invalid or expired + throw new Error('NFE.io authentication failed. Check your API key.'); + } + if (error instanceof ValidationError) { + // Request data is invalid + const details = error.details as any; + throw new Error(`Invalid invoice data: ${JSON.stringify(details)}`); + } + if (error instanceof NotFoundError) { + // Company or resource not found + throw new Error(`Company ${companyId} not found`); + } + if (error instanceof PollingTimeoutError) { + // Invoice is still processing, not an error per se + console.warn('Invoice still processing, will check again later'); + return null; // Handle async follow-up + } + if (error instanceof InvoiceProcessingError) { + // Municipality rejected the invoice + const details = error.details as any; + throw new Error(`Invoice rejected: ${details?.flowMessage || error.message}`); + } + if (error instanceof RateLimitError) { + // Shouldn't happen (auto-retried) but handle just in case + throw new Error('Rate limited by NFE.io API'); + } + if (error instanceof TimeoutError) { + // Request timeout (not polling timeout) + throw new Error('NFE.io API request timed out'); + } + // Unknown error + throw error; + } +} +``` + +### Logging Errors + +```typescript +catch (error) { + if (error instanceof NfeError) { + // Structured logging + console.error(JSON.stringify(error.toJSON())); + // Output: { type, message, code, details, raw } + } +} +``` + +### Retry Manual Operations + +For operations not automatically retried (like 400 errors after fixing data): + +```typescript +async function retryWithFix(fn: () => Promise, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (error instanceof ValidationError && i < maxRetries - 1) { + // Log and attempt to fix data before retry + console.warn(`Validation error (attempt ${i + 1}):`, error.details); + continue; + } + throw error; + } + } +} +``` diff --git a/skills/nfeio-sdk/references/product-invoices-and-taxes.md b/skills/nfeio-sdk/references/product-invoices-and-taxes.md new file mode 100644 index 0000000..f8d8970 --- /dev/null +++ b/skills/nfeio-sdk/references/product-invoices-and-taxes.md @@ -0,0 +1,352 @@ +# Product Invoices (NF-e), Taxes, CT-e & Inbound Documents + +Detailed reference for resources hosted at `api.nfse.io` (CT-e/product API). + +## ProductInvoicesResource (NF-e Issuance) + +Access via `nfe.productInvoices`. Company-scoped. + +**Important**: All product invoice operations are **always async** (return 202). There is NO `createAndWait()`. Completion is notified via **webhooks**. + +### create(companyId, data): Promise + +Issues a product invoice. Returns immediately with 202 status. The invoice will be processed asynchronously and the result delivered via webhook. + +```typescript +// Simplified NfeProductInvoiceIssueData structure +{ + // Buyer info + buyer: { + personType: NfePersonType; // 'NaturalPerson' | 'LegalEntity' | 'Foreign' + federalTaxNumber?: string; + name: string; + email?: string; + address: NfeAddress; + stateTaxNumber?: string; // IE (Inscrição Estadual) + stateTaxIndicator?: NfeReceiverStateTaxIndicator; + }; + + // Items + items: Array<{ + code: string; + description: string; + ncm: string; // 8-digit NCM code + cfop: number; // CFOP + quantity: number; + unitAmount: number; + totalAmount: number; + tax: NfeInvoiceItemTax; // ICMS, IPI, PIS, COFINS, II + origin: number; // Tax origin code + // ... many more optional fields + }>; + + // Operation details + operationType: NfeOperationType; // 'Outgoing' | 'Incoming' + purposeType?: NfePurposeType; // 'Normal' | 'Complement' | 'Adjustment' | 'Devolution' + consumerPresenceType?: NfeConsumerPresenceType; + + // Payment + payment: NfePaymentResource; + + // Optional sections + transport?: NfeTransportInformation; + additionalInformation?: NfeAdditionalInformation; + billing?: NfeBillingResource; +} +``` + +### createWithStateTax(companyId, stateTaxId, data): Promise + +Same as `create()` but uses a specific state tax registration (IE) for issuance. Use when company has multiple IEs across states. + +### list(companyId, options): Promise + +**`environment` is REQUIRED**. Uses cursor-based pagination (not offset). + +```typescript +interface NfeProductInvoiceListOptions { + environment: NfeEnvironmentType; // 'Production' | 'Test' -- REQUIRED + startingAfter?: string; // Cursor: invoice ID for next page + endingBefore?: string; // Cursor: invoice ID for previous page + limit?: number; // Items per page (default: 25) + q?: string; // ElasticSearch query string +} +``` + +### retrieve(companyId, invoiceId): Promise + +Returns complete invoice with events. + +### cancel(companyId, invoiceId, reason?): Promise + +Cancels invoice. Always async (204 response). Reason string is optional. + +### listItems(companyId, invoiceId, options?): Promise + +### listEvents(companyId, invoiceId, options?): Promise + +### downloadPdf(companyId, invoiceId): Promise + +Returns the DANFE PDF as a raw Buffer. + +### downloadXml(companyId, invoiceId): Promise + +Returns the NF-e XML. + +### downloadRejectionXml(companyId, invoiceId): Promise + +XML for rejected invoices. + +### downloadEpecXml(companyId, invoiceId): Promise + +XML for EPEC contingency mode. + +### sendCorrectionLetter(companyId, invoiceId, data): Promise + +Issues a Carta de Correao Eletrnica (CC-e): + +```typescript +await nfe.productInvoices.sendCorrectionLetter(companyId, invoiceId, { + correctionText: 'Correction description here', + // Must be 15-1000 characters + // No accents or special characters allowed +}); +``` + +### disable(companyId, data): Promise + +Inutilizao (disablement) of invoice number. Prevents specific numbers from being used. + +```typescript +interface NfeDisablementData { + stateTaxId: string; + environment: NfeEnvironmentType; + invoiceSeries: number; + invoiceNumberStart: number; + invoiceNumberEnd: number; + reason: string; +} +``` + +### disableRange(companyId, data): Promise + +Disable a range of invoice numbers at once. + +--- + +## StateTaxesResource (Inscrio Estadual) + +Access via `nfe.stateTaxes`. Company-scoped. **Prerequisite for NF-e issuance.** + +```typescript +interface NfeStateTax { + id: string; + type: NfeStateTaxType; // 'nFe' | 'nFce' | 'cTe' + state: NfeStateTaxStateCode; + taxNumber: string; // IE number + environment: NfeStateTaxEnvironmentType; // 'Production' | 'Test' + status: NfeStateTaxStatus; + specialTaxRegime?: NfeSpecialTaxRegime; + securityCredential?: NfeSecurityCredential; +} +``` + +| Method | Signature | +|--------|-----------| +| `list` | `(companyId, options?): Promise` | +| `create` | `(companyId, data): Promise` | +| `retrieve` | `(companyId, stateTaxId): Promise` | +| `update` | `(companyId, stateTaxId, data): Promise` | +| `delete` | `(companyId, stateTaxId): Promise` | + +Uses cursor-based pagination like product invoices. + +--- + +## TaxCalculationResource + +Access via `nfe.taxCalculation`. Tenant-scoped (uses `tenantId`). + +### calculate(tenantId, request): Promise + +Computes Brazilian taxes for product operations. + +```typescript +interface CalculateRequest { + operationType: TaxOperationType; // 'Incoming' | 'Outgoing' + issuer: { + state: BrazilianState; // 'SP', 'RJ', 'MG', etc. + taxRegime: TaxCalcTaxRegime; // 'RealProfit' | 'SimplesNacional' | ... + stateTaxNumber?: string; // IE + }; + recipient: { + state: BrazilianState; + taxRegime?: TaxCalcTaxRegime; + }; + items: CalculateItemRequest[]; +} + +interface CalculateItemRequest { + id: string; // Your item identifier + operationCode: number; // CFOP number + origin: TaxOrigin; // 'National' | 'Foreign' | ... + quantity: number; + unitAmount: number; + ncm: string; // 8-digit NCM code + // Optional tax overrides: + icms?: Partial; + pis?: Partial; + cofins?: Partial; + ipi?: Partial; + ii?: Partial; +} +``` + +**Response**: +```typescript +interface CalculateResponse { + items?: CalculateItemResponse[]; +} + +interface CalculateItemResponse { + id: string; + cfop?: number; // CFOP determined by engine + icms?: TaxIcms; // ICMS breakdown + icmsUfDest?: TaxIcmsUfDest; // DIFAL (interestadual) + pis?: TaxPis; + cofins?: TaxCofins; + ipi?: TaxIpi; + ii?: TaxIi; // Import tax +} +``` + +Each tax type includes fields like: `cst` (tax situation code), `vBC` (tax base), `pTax` (rate), `vTax` (amount). + +--- + +## TaxCodesResource + +Access via `nfe.taxCodes`. Global scope. Reference data for tax calculation inputs. + +| Method | Returns | +|--------|---------| +| `listOperationCodes(options?)` | CFOP codes | +| `listAcquisitionPurposes(options?)` | Acquisition purpose codes | +| `listIssuerTaxProfiles(options?)` | Issuer tax profile codes | +| `listRecipientTaxProfiles(options?)` | Recipient tax profile codes | + +All return `TaxCodePaginatedResponse` with `{ data: TaxCode[], totalCount, page }`. + +```typescript +interface TaxCode { + code: string; + description: string; +} +``` + +--- + +## TransportationInvoicesResource (CT-e) + +Access via `nfe.transportationInvoices`. Company-scoped. +Uses CT-e API host (`api.nfse.io`). + +### enable(companyId, options?): Promise + +Enable automatic CT-e fetch via SEFAZ Distribuio DFe: + +```typescript +await nfe.transportationInvoices.enable(companyId, { + startFromNsu?: number, // Start from specific NSU + startFromDate?: string, // ISO 8601 date +}); +``` + +### disable(companyId): Promise + +### getSettings(companyId): Promise + +### retrieve(companyId, accessKey): Promise + +Get CT-e metadata by 44-digit access key. + +### retrieveEvent(companyId, accessKey): Promise + +### downloadXml(companyId, accessKey): Promise + +### downloadEventXml(companyId, accessKey): Promise + +--- + +## InboundProductInvoicesResource (NF-e Distribution) + +Access via `nfe.inboundProductInvoices`. Company-scoped. +Uses CT-e API host (`api.nfse.io`). + +### enableAutoFetch(companyId, options?): Promise + +```typescript +interface EnableInboundOptions { + startFromNsu?: string; + startFromDate?: string; // ISO 8601 + environmentSEFAZ?: string; + automaticManifesting?: AutomaticManifesting; + webhookVersion?: string; +} +``` + +### disableAutoFetch(companyId): Promise + +### getSettings(companyId): Promise + +### Document Access Methods + +| Method | Input | Returns | +|--------|-------|---------| +| `getDetails(companyId, accessKey)` | 44-digit key | Generic metadata | +| `getProductInvoiceDetails(companyId, accessKey)` | 44-digit key | Product invoice metadata | +| `getEventDetails(companyId, accessKey)` | 44-digit key | Event metadata | +| `getProductInvoiceEventDetails(companyId, accessKey)` | 44-digit key | Product invoice event metadata | +| `getXml(companyId, accessKey)` | 44-digit key | XML string | +| `getEventXml(companyId, accessKey)` | 44-digit key | Event XML string | +| `getPdf(companyId, accessKey)` | 44-digit key | PDF string (base64) | +| `getJson(companyId, accessKey, format?)` | 44-digit key | JSON string | + +### manifest(companyId, accessKey, eventType): Promise + +Send Manifestao do Destinatrio: + +```typescript +type ManifestEventType = + | 210210 // Cincia da Operao (awareness) + | 210220 // Confirmao da Operao (confirmation) + | 210240; // Operao No Realizada (not performed) +``` + +### reprocessWebhook(companyId, accessKey): Promise + +Resend webhook notification for a specific document. + +--- + +## NF-e Product Invoice Lifecycle + +``` +1. Create state tax registration (IE) + nfe.stateTaxes.create(companyId, { ... }) + +2. Create product invoice (always async) + nfe.productInvoices.create(companyId, data) + -> Returns 202 immediately + +3. Receive webhook notification + invoice.issued / invoice.failed + +4. Download files + nfe.productInvoices.downloadPdf(companyId, id) + nfe.productInvoices.downloadXml(companyId, id) + +5. If needed: correction letter or cancellation + nfe.productInvoices.sendCorrectionLetter(companyId, id, { correctionText }) + nfe.productInvoices.cancel(companyId, id) +``` diff --git a/skills/nfeio-sdk/references/service-invoices-and-polling.md b/skills/nfeio-sdk/references/service-invoices-and-polling.md new file mode 100644 index 0000000..ed04717 --- /dev/null +++ b/skills/nfeio-sdk/references/service-invoices-and-polling.md @@ -0,0 +1,335 @@ +# Service Invoices, Companies, People & Webhooks + +Detailed reference for the main API resources hosted at `api.nfe.io/v1`. + +## ServiceInvoicesResource + +Access via `nfe.serviceInvoices`. All methods are company-scoped. + +### create(companyId, data): Promise + +Returns a **discriminated union** — not a raw invoice: + +```typescript +type CreateInvoiceResponse = + | { status: 'immediate'; invoice: ServiceInvoiceData } + | { status: 'async'; response: ServiceInvoiceAsyncResponse }; + +interface ServiceInvoiceAsyncResponse { + code: 202; + status: 'pending'; + location: string; + invoiceId: string; // Extracted from Location header +} +``` + +**CreateServiceInvoiceData** key fields: +```typescript +{ + cityServiceCode: string; // Municipal service code + description: string; // Service description + servicesAmount: number; // Total service amount + borrower: { + federalTaxNumber: number; // CNPJ (14 digits) or CPF (11 digits) as NUMBER + name: string; + email?: string; + address?: { + country: string; + postalCode: string; + street: string; + number: string; + additionalInformation?: string; + district: string; + city: { code: string; name: string }; + state: string; + }; + }; + // Optional fields: + deductions?: number; + discountConditionedAmount?: number; + discountUnconditionedAmount?: number; + issRate?: number; + issTaxAmount?: number; + irAmountWithheld?: number; + pisAmountWithheld?: number; + cofinsAmountWithheld?: number; + csllAmountWithheld?: number; + inssAmountWithheld?: number; + issAmountWithheld?: number; +} +``` + +### createAndWait(companyId, data, options?): Promise + +Recommended method. Creates invoice and polls until terminal state. + +```typescript +interface PollingOptions { + timeout?: number; // Total timeout in ms (default: 120000 = 2 min) + initialDelay?: number; // Delay before first poll (default: 1000) + maxDelay?: number; // Max delay between polls (default: 10000) + backoffFactor?: number; // Exponential backoff multiplier (default: 1.5) + onPoll?: (attempt: number, flowStatus: FlowStatus) => void; +} +``` + +Throws `PollingTimeoutError` if timeout exceeded. Throws `InvoiceProcessingError` if invoice reaches `IssueFailed` or `CancelFailed`. + +### list(companyId, options?): Promise + +```typescript +interface ListServiceInvoicesOptions { + pageIndex?: number; // 0-based page (default: 0) + pageCount?: number; // Items per page (default: 50) + issuedBegin?: string; // Filter by issue date start (yyyy-MM-dd) + issuedEnd?: string; // Filter by issue date end + createdBegin?: string; // Filter by creation date start + createdEnd?: string; // Filter by creation date end + hasTotals?: boolean; // Include totals in response +} +``` + +Returns `{ data: ServiceInvoiceData[], totalCount: number, page: PageInfo }`. + +### retrieve(companyId, invoiceId): Promise + +Get a single invoice by ID. + +### cancel(companyId, invoiceId): Promise + +Cancel an issued invoice. May return async (some municipalities process cancellation asynchronously). + +### sendEmail(companyId, invoiceId): Promise + +Send invoice to borrower's email. Returns `{ sent: boolean, message?: string }`. + +### downloadPdf(companyId, invoiceId?): Promise + +Download DANFE PDF. If `invoiceId` is omitted, downloads a ZIP of all invoices for the company. + +### downloadXml(companyId, invoiceId?): Promise + +Download RPS XML. Same ZIP behavior when `invoiceId` is omitted. + +### getStatus(companyId, invoiceId): Promise + +Returns `{ flowStatus: FlowStatus, invoice: ServiceInvoiceData, isComplete: boolean, isFailed: boolean }`. + +### createBatch(companyId, invoices, options?): Promise> + +Batch creation with optional concurrency control: +```typescript +options?: { + waitForCompletion?: boolean; // Use createAndWait for each + maxConcurrent?: number; // Parallel limit +} +``` + +## FlowStatus Values + +| Status | Terminal | Meaning | +|--------|----------|---------| +| `Issued` | Yes | Invoice successfully issued | +| `IssueFailed` | Yes | Issuance failed | +| `Cancelled` | Yes | Successfully cancelled | +| `CancelFailed` | Yes | Cancellation failed | +| `WaitingSend` | No | Queued to send to municipality | +| `WaitingReturn` | No | Waiting for municipality response | +| `WaitingDownload` | No | Waiting to download result | +| `WaitingCalculateTaxes` | No | Tax calculation in progress | +| `WaitingDefineRpsNumber` | No | Assigning RPS number | +| `WaitingSendCancel` | No | Cancellation queued | +| `PullFromCityHall` | No | Fetching from city hall | + +--- + +## CompaniesResource + +Access via `nfe.companies`. NOT company-scoped (operates at account level). + +### create(data): Promise + +```typescript +// Company entity key fields +interface Company { + id: string; + name: string; + tradeName?: string; + federalTaxNumber: number; // CNPJ (14 digits) or CPF (11 digits) as NUMBER + email: string; + address?: Address; + // Auto-populated: + createdOn?: string; + modifiedOn?: string; +} +``` + +Validates CNPJ (14-digit check digit algorithm) and CPF (11-digit check digit algorithm) before sending. + +### list(options?): Promise> + +Offset pagination: `{ pageIndex: number, pageCount: number }`. + +### listAll(): Promise + +Auto-paginates through all pages. Returns complete array. + +### listIterator(): AsyncIterableIterator + +Async iterator for memory-efficient pagination: +```typescript +for await (const company of nfe.companies.listIterator()) { + console.log(company.name); +} +``` + +### retrieve(companyId): Promise + +### update(companyId, data): Promise + +### remove(companyId): Promise<{ deleted: boolean; id: string }> + +Named `remove()` (not `delete()`) to avoid JS keyword conflict. + +### Certificate Management + +```typescript +// Validate certificate before upload +const result = await nfe.companies.validateCertificate(certBuffer, password); +// result: { valid: boolean, metadata?: CertificateMetadata } + +// Upload certificate to company +await nfe.companies.uploadCertificate(companyId, certBuffer, password); + +// Get certificate status +const status = await nfe.companies.getCertificateStatus(companyId); +// { hasCertificate: boolean, expiresOn?: string, status?: string } + +// Replace existing certificate +await nfe.companies.replaceCertificate(companyId, certBuffer, password); + +// Check expiration (returns days remaining) +const exp = await nfe.companies.checkCertificateExpiration(companyId); +// { daysRemaining: number, expiresOn?: string } +``` + +### Search Helpers + +```typescript +// Find company by CNPJ/CPF (returns null if not found) +const company = await nfe.companies.findByTaxNumber(12345678000190); + +// Search by name (returns array) +const matches = await nfe.companies.findByName('Example'); + +// Companies with valid certificates +const withCerts = await nfe.companies.getCompaniesWithCertificates(); + +// Companies with expiring certificates (within N days) +const expiring = await nfe.companies.getCompaniesWithExpiringCertificates(30); +``` + +--- + +## LegalPeopleResource + +Access via `nfe.legalPeople`. Company-scoped (legal entities / PJ). + +All methods: `method(companyId, ...)` + +```typescript +interface LegalPerson { + id?: string; + federalTaxNumber: string; // CNPJ as STRING (not number) + name: string; + email?: string; + address?: Address; +} +``` + +| Method | Signature | +|--------|-----------| +| `list` | `(companyId): Promise>` | +| `create` | `(companyId, data): Promise` | +| `retrieve` | `(companyId, personId): Promise` | +| `update` | `(companyId, personId, data): Promise` | +| `delete` | `(companyId, personId): Promise` | +| `createBatch` | `(companyId, items[]): Promise` | +| `findByTaxNumber` | `(companyId, taxNumber): Promise` | + +`findByTaxNumber` returns `undefined` (not throwing `NotFoundError`) when not found. + +--- + +## NaturalPeopleResource + +Access via `nfe.naturalPeople`. Company-scoped (natural persons / PF). + +Same API as LegalPeopleResource but with `NaturalPerson` type (uses CPF instead of CNPJ). + +```typescript +interface NaturalPerson { + id?: string; + federalTaxNumber: string; // CPF as STRING + name: string; + email?: string; + address?: Address; +} +``` + +--- + +## WebhooksResource + +Access via `nfe.webhooks`. Company-scoped. + +```typescript +interface Webhook { + id?: string; + url: string; + events: WebhookEvent[]; + active?: boolean; + secret?: string; + createdOn?: string; + modifiedOn?: string; +} + +type WebhookEvent = + | 'invoice.created' + | 'invoice.issued' + | 'invoice.cancelled' + | 'invoice.failed'; +``` + +| Method | Signature | +|--------|-----------| +| `list` | `(companyId): Promise>` | +| `create` | `(companyId, data): Promise` | +| `retrieve` | `(companyId, webhookId): Promise` | +| `update` | `(companyId, webhookId, data): Promise` | +| `delete` | `(companyId, webhookId): Promise` | +| `validateSignature` | `(payload, signature, secret): boolean` | +| `test` | `(companyId, webhookId): Promise<{ success: boolean }>` | +| `getAvailableEvents` | `(): WebhookEvent[]` | + +### Webhook Signature Validation + +In your webhook handler, validate the `X-Hub-Signature` header: + +```typescript +app.post('/webhooks/nfe', (req, res) => { + const signature = req.headers['x-hub-signature'] as string; + const rawBody = req.body; // raw string, not parsed JSON + + const isValid = nfe.webhooks.validateSignature(rawBody, signature, webhookSecret); + if (!isValid) { + return res.status(401).send('Invalid signature'); + } + + const event = JSON.parse(rawBody); + // Handle event... + res.status(200).send('OK'); +}); +``` + +Uses HMAC-SHA1 for signature verification.