Skip to content

Commit 37c5cb2

Browse files
feat: add AddressesResource for address lookup and search functionality
- Implemented AddressesResource to handle address lookup operations via the Address API. - Added methods for looking up addresses by postal code and searching by term or filter. - Introduced validation for postal codes and search terms. - Updated NfeConfig to support separate API keys for Address API. - Created integration tests for AddressesResource to ensure functionality. - Added unit tests for AddressesResource covering various scenarios and validations. - Updated types to include address-related interfaces and response structures. - Enhanced error handling for invalid inputs and API responses.
1 parent e757c91 commit 37c5cb2

25 files changed

+1559
-120
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,13 +337,42 @@ const ehValido = nfe.webhooks.validateSignature(
337337
);
338338
```
339339

340+
#### 📍 Endereços (`nfe.addresses`)
341+
342+
Consultar endereços brasileiros por CEP ou termo de busca:
343+
344+
```typescript
345+
// Buscar endereço por CEP
346+
const endereco = await nfe.addresses.lookupByPostalCode('01310-100');
347+
console.log(endereco.street); // 'Avenida Paulista'
348+
console.log(endereco.city.name); // 'São Paulo'
349+
console.log(endereco.state); // 'SP'
350+
351+
// Buscar por termo (nome de rua, bairro, etc.)
352+
const resultado = await nfe.addresses.lookupByTerm('Paulista');
353+
for (const end of resultado.addresses) {
354+
console.log(`${end.postalCode}: ${end.street}, ${end.city.name}`);
355+
}
356+
357+
// Buscar com filtro OData
358+
const filtrado = await nfe.addresses.search({
359+
filter: "city.name eq 'São Paulo'"
360+
});
361+
```
362+
363+
> **Nota:** A API de Endereços usa um host separado (`address.api.nfe.io`). Você pode configurar uma chave API específica com `addressApiKey`, ou o SDK usará `apiKey` como fallback.
364+
340365
### Opções de Configuração
341366

342367
```typescript
343368
const nfe = new NfeClient({
344-
// Obrigatório: Sua chave API do NFE.io
369+
// Chave API principal do NFE.io (opcional se usar apenas Addresses com addressApiKey)
345370
apiKey: 'sua-chave-api',
346371

372+
// Opcional: Chave API específica para consulta de endereços
373+
// Se não fornecida, usa apiKey como fallback
374+
addressApiKey: 'sua-chave-address-api',
375+
347376
// Opcional: Ambiente (padrão: 'production')
348377
environment: 'production', // ou 'sandbox'
349378

@@ -363,6 +392,24 @@ const nfe = new NfeClient({
363392
});
364393
```
365394

395+
#### Variáveis de Ambiente
396+
397+
O SDK suporta as seguintes variáveis de ambiente:
398+
399+
| Variável | Descrição |
400+
|----------|-----------|
401+
| `NFE_API_KEY` | Chave API principal (fallback para `apiKey`) |
402+
| `NFE_ADDRESS_API_KEY` | Chave API para endereços (fallback para `addressApiKey`) |
403+
404+
```bash
405+
# Configurar via ambiente
406+
export NFE_API_KEY="sua-chave-api"
407+
export NFE_ADDRESS_API_KEY="sua-chave-address"
408+
409+
# Usar SDK sem passar chaves no código
410+
const nfe = new NfeClient({});
411+
```
412+
366413
### Tratamento de Erros
367414

368415
O SDK fornece classes de erro tipadas:

examples/address-lookup.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* NFE.io SDK v3 - Address Lookup Examples
3+
*
4+
* This example demonstrates how to use the Addresses API for looking up
5+
* Brazilian addresses (CEP/postal code lookups).
6+
*
7+
* Prerequisites:
8+
* - Set NFE_ADDRESS_API_KEY or NFE_API_KEY environment variable
9+
* - Or pass the API key directly in the configuration
10+
*
11+
* Run this example:
12+
* node examples/address-lookup.js
13+
*/
14+
15+
import { NfeClient } from '../dist/index.js';
16+
17+
// Configuration with separate address API key (optional)
18+
const client = new NfeClient({
19+
// You can use a separate API key for address lookups
20+
// addressApiKey: process.env.NFE_ADDRESS_API_KEY,
21+
22+
// Or use the main API key (will be used as fallback for addresses)
23+
apiKey: process.env.NFE_API_KEY,
24+
25+
// Environment: 'production' or 'development'
26+
environment: 'production',
27+
});
28+
29+
/**
30+
* Example 1: Basic postal code lookup
31+
*/
32+
async function lookupByPostalCode() {
33+
console.log('\n📮 Example 1: Postal Code Lookup');
34+
console.log('='.repeat(50));
35+
36+
try {
37+
// Lookup a São Paulo CEP (Avenida Paulista)
38+
const result = await client.addresses.lookupByPostalCode('01310-100');
39+
40+
console.log('CEP:', result.postalCode);
41+
console.log('Street:', result.street);
42+
console.log('District:', result.district);
43+
console.log('City:', result.city?.name);
44+
console.log('State:', result.state);
45+
console.log('Country:', result.country);
46+
47+
// Works with or without hyphen
48+
const result2 = await client.addresses.lookupByPostalCode('01310100');
49+
console.log('\nSame CEP without hyphen:', result2.postalCode);
50+
} catch (error) {
51+
console.error('Error:', error.message);
52+
}
53+
}
54+
55+
/**
56+
* Example 2: Search addresses by term
57+
*/
58+
async function lookupByTerm() {
59+
console.log('\n🔍 Example 2: Search by Term');
60+
console.log('='.repeat(50));
61+
62+
try {
63+
const result = await client.addresses.lookupByTerm('Avenida Paulista');
64+
65+
console.log('Search results:');
66+
if (result.addresses && result.addresses.length > 0) {
67+
for (const address of result.addresses.slice(0, 3)) {
68+
console.log(` - ${address.postalCode}: ${address.street}, ${address.city?.name}/${address.state}`);
69+
}
70+
if (result.addresses.length > 3) {
71+
console.log(` ... and ${result.addresses.length - 3} more`);
72+
}
73+
} else {
74+
console.log(' No addresses found');
75+
}
76+
} catch (error) {
77+
console.error('Error:', error.message);
78+
}
79+
}
80+
81+
/**
82+
* Example 3: Search with OData filter
83+
*/
84+
async function searchWithFilter() {
85+
console.log('\n🎯 Example 3: Search with Filter');
86+
console.log('='.repeat(50));
87+
88+
try {
89+
// Search addresses in São Paulo
90+
const result = await client.addresses.search({
91+
filter: "city.name eq 'São Paulo'",
92+
});
93+
94+
console.log('Filtered search results:');
95+
if (result.addresses && result.addresses.length > 0) {
96+
console.log(` Found ${result.addresses.length} addresses in São Paulo`);
97+
} else {
98+
console.log(' No addresses found');
99+
}
100+
} catch (error) {
101+
console.error('Error:', error.message);
102+
}
103+
}
104+
105+
/**
106+
* Example 4: Using only addressApiKey (isolated usage)
107+
*/
108+
async function isolatedAddressUsage() {
109+
console.log('\n🔐 Example 4: Isolated Address API Usage');
110+
console.log('='.repeat(50));
111+
112+
// Create a client with ONLY addressApiKey
113+
// This is useful when you only have access to the Address API
114+
const addressOnlyClient = new NfeClient({
115+
addressApiKey: process.env.NFE_ADDRESS_API_KEY || process.env.NFE_API_KEY,
116+
});
117+
118+
try {
119+
// Addresses work
120+
const result = await addressOnlyClient.addresses.lookupByPostalCode('20040-020');
121+
console.log('Rio de Janeiro CEP lookup succeeded!');
122+
console.log(` ${result.street}, ${result.city?.name}/${result.state}`);
123+
} catch (error) {
124+
console.error('Error:', error.message);
125+
}
126+
127+
// Other resources will throw an error (commented out to avoid runtime error)
128+
// This would throw: "API key required for this resource"
129+
// await addressOnlyClient.serviceInvoices.list('company-id');
130+
}
131+
132+
/**
133+
* Example 5: Error handling
134+
*/
135+
async function errorHandling() {
136+
console.log('\n⚠️ Example 5: Error Handling');
137+
console.log('='.repeat(50));
138+
139+
try {
140+
// Invalid CEP format
141+
await client.addresses.lookupByPostalCode('invalid');
142+
} catch (error) {
143+
console.log('ValidationError for invalid CEP:', error.message);
144+
}
145+
146+
try {
147+
// Empty search term
148+
await client.addresses.lookupByTerm('');
149+
} catch (error) {
150+
console.log('ValidationError for empty term:', error.message);
151+
}
152+
153+
try {
154+
// Non-existent CEP (will get NotFoundError from API)
155+
await client.addresses.lookupByPostalCode('00000000');
156+
} catch (error) {
157+
console.log('API error for non-existent CEP:', error.message);
158+
}
159+
}
160+
161+
/**
162+
* Main function to run all examples
163+
*/
164+
async function main() {
165+
console.log('🏠 NFE.io Address Lookup Examples');
166+
console.log('━'.repeat(50));
167+
168+
if (!process.env.NFE_API_KEY && !process.env.NFE_ADDRESS_API_KEY) {
169+
console.error('\n❌ No API key found!');
170+
console.error('Please set NFE_API_KEY or NFE_ADDRESS_API_KEY environment variable.');
171+
console.error('\nExample:');
172+
console.error(' export NFE_API_KEY="your-api-key"');
173+
console.error(' node examples/address-lookup.js');
174+
process.exit(1);
175+
}
176+
177+
await lookupByPostalCode();
178+
await lookupByTerm();
179+
await searchWithFilter();
180+
await isolatedAddressUsage();
181+
await errorHandling();
182+
183+
console.log('\n✅ All examples completed!');
184+
}
185+
186+
// Run main function
187+
main().catch(console.error);
Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,72 @@
11
## 1. Types & Generated Code
22

3-
- [ ] 1.1 Generate TypeScript types from `openapi/spec/consulta-endereco.yaml` using openapi-typescript
4-
- [ ] 1.2 Create `src/generated/consulta-endereco.ts` with generated types
5-
- [ ] 1.3 Export Address-related types from `src/generated/index.ts`
3+
- [x] 1.1 Generate TypeScript types from `openapi/spec/consulta-endereco.yaml` using openapi-typescript
4+
- Note: Swagger 2.0 not supported by openapi-typescript v6+, types created manually
5+
- [x] 1.2 Create `src/generated/consulta-endereco.ts` with generated types
6+
- [x] 1.3 Export Address-related types from `src/generated/index.ts`
67

78
## 2. Core Types Extension
89

9-
- [ ] 2.1 Add `addressApiKey?: string` to `NfeConfig` interface in `src/core/types.ts`
10-
- [ ] 2.2 Add `Address` and `AddressLookupResponse` types to `src/core/types.ts`
11-
- [ ] 2.3 Add `AddressSearchOptions` type with `filter` field
10+
- [x] 2.1 Add `addressApiKey?: string` to `NfeConfig` interface in `src/core/types.ts`
11+
- [x] 2.2 Add `Address` and `AddressLookupResponse` types to `src/core/types.ts`
12+
- [x] 2.3 Add `AddressSearchOptions` type with `filter` field
1213

1314
## 3. Multi-API Support Infrastructure
1415

15-
- [ ] 3.1 Refactor `NfeClient` constructor to not require `apiKey` (make it optional)
16-
- [ ] 3.2 Add `resolveAddressApiKey()` private method with fallback chain logic
17-
- [ ] 3.3 Add `resolveMainApiKey()` private method for existing resources
18-
- [ ] 3.4 Add `getEnvironmentVariable()` helper method for reading `NFE_ADDRESS_API_KEY`
19-
- [ ] 3.5 Convert `serviceInvoices`, `companies`, `legalPeople`, `naturalPeople`, `webhooks` to lazy getters with validation
16+
- [x] 3.1 Refactor `NfeClient` constructor to not require `apiKey` (make it optional)
17+
- [x] 3.2 Add `resolveAddressApiKey()` private method with fallback chain logic
18+
- [x] 3.3 Add `resolveMainApiKey()` private method for existing resources
19+
- [x] 3.4 Add `getEnvironmentVariable()` helper method for reading `NFE_ADDRESS_API_KEY`
20+
- [x] 3.5 Convert `serviceInvoices`, `companies`, `legalPeople`, `naturalPeople`, `webhooks` to lazy getters with validation
2021

2122
## 4. Addresses Resource Implementation
2223

23-
- [ ] 4.1 Create `src/core/resources/addresses.ts` with `AddressesResource` class
24-
- [ ] 4.2 Implement `lookupByPostalCode(postalCode: string)` method
25-
- [ ] 4.3 Implement `search(options: AddressSearchOptions)` method with `$filter` query param
26-
- [ ] 4.4 Implement `lookupByTerm(term: string)` method
27-
- [ ] 4.5 Add input validation for postal code format (8 digits, with or without hyphen)
28-
- [ ] 4.6 Add input validation for empty term
29-
- [ ] 4.7 Export `AddressesResource` from `src/core/resources/index.ts`
24+
- [x] 4.1 Create `src/core/resources/addresses.ts` with `AddressesResource` class
25+
- [x] 4.2 Implement `lookupByPostalCode(postalCode: string)` method
26+
- [x] 4.3 Implement `search(options: AddressSearchOptions)` method with `$filter` query param
27+
- [x] 4.4 Implement `lookupByTerm(term: string)` method
28+
- [x] 4.5 Add input validation for postal code format (8 digits, with or without hyphen)
29+
- [x] 4.6 Add input validation for empty term
30+
- [x] 4.7 Export `AddressesResource` from `src/core/resources/index.ts`
3031

3132
## 5. Client Integration
3233

33-
- [ ] 5.1 Add lazy `addresses` getter to `NfeClient` class
34-
- [ ] 5.2 Create separate `HttpClient` instance for addresses with `https://address.api.nfe.io/v2` base URL
35-
- [ ] 5.3 Throw `ConfigurationError` with descriptive message when no API key available
36-
- [ ] 5.4 Export Address types from `src/index.ts`
34+
- [x] 5.1 Add lazy `addresses` getter to `NfeClient` class
35+
- [x] 5.2 Create separate `HttpClient` instance for addresses with `https://address.api.nfe.io/v2` base URL
36+
- [x] 5.3 Throw `ConfigurationError` with descriptive message when no API key available
37+
- [x] 5.4 Export Address types from `src/index.ts`
3738

3839
## 6. Unit Tests
3940

40-
- [ ] 6.1 Create `tests/unit/resources/addresses.test.ts`
41-
- [ ] 6.2 Test `lookupByPostalCode()` with valid CEP returns correct response
42-
- [ ] 6.3 Test `lookupByPostalCode()` with invalid CEP throws `ValidationError`
43-
- [ ] 6.4 Test `lookupByTerm()` with empty term throws `ValidationError`
44-
- [ ] 6.5 Test `search()` passes filter to query params correctly
45-
- [ ] 6.6 Create `tests/unit/client-multikey.test.ts` for multi-API key tests
46-
- [ ] 6.7 Test lazy getter throws when no key available
47-
- [ ] 6.8 Test fallback chain: `addressApiKey``apiKey` → env vars
48-
- [ ] 6.9 Test isolated resource usage (only addresses, no apiKey)
41+
- [x] 6.1 Create `tests/unit/resources/addresses.test.ts`
42+
- [x] 6.2 Test `lookupByPostalCode()` with valid CEP returns correct response
43+
- [x] 6.3 Test `lookupByPostalCode()` with invalid CEP throws `ValidationError`
44+
- [x] 6.4 Test `lookupByTerm()` with empty term throws `ValidationError`
45+
- [x] 6.5 Test `search()` passes filter to query params correctly
46+
- [x] 6.6 Create `tests/unit/client-multikey.test.ts` for multi-API key tests
47+
- [x] 6.7 Test lazy getter throws when no key available
48+
- [x] 6.8 Test fallback chain: `addressApiKey``apiKey` → env vars
49+
- [x] 6.9 Test isolated resource usage (only addresses, no apiKey)
4950

5051
## 7. Integration Tests
5152

52-
- [ ] 7.1 Create `tests/integration/addresses.integration.test.ts`
53-
- [ ] 7.2 Test real API call to lookup by postal code (requires test API key)
54-
- [ ] 7.3 Test real API call to search by term
55-
- [ ] 7.4 Test 404 response handling for non-existent CEP
53+
- [x] 7.1 Create `tests/integration/addresses.integration.test.ts`
54+
- [x] 7.2 Test real API call to lookup by postal code (requires test API key)
55+
- [x] 7.3 Test real API call to search by term
56+
- [x] 7.4 Test 404 response handling for non-existent CEP
5657

5758
## 8. Documentation & Examples
5859

59-
- [ ] 8.1 Create `examples/address-lookup.js` with usage examples
60-
- [ ] 8.2 Update `README.md` with Addresses resource documentation
61-
- [ ] 8.3 Document `addressApiKey` configuration option
62-
- [ ] 8.4 Document environment variables `NFE_ADDRESS_API_KEY`
63-
- [ ] 8.5 Add JSDoc comments to all public methods in `AddressesResource`
60+
- [x] 8.1 Create `examples/address-lookup.js` with usage examples
61+
- [x] 8.2 Update `README.md` with Addresses resource documentation
62+
- [x] 8.3 Document `addressApiKey` configuration option
63+
- [x] 8.4 Document environment variables `NFE_ADDRESS_API_KEY`
64+
- [x] 8.5 Add JSDoc comments to all public methods in `AddressesResource`
6465

6566
## 9. Final Validation
6667

67-
- [ ] 9.1 Run `npm run typecheck` - ensure zero errors
68-
- [ ] 9.2 Run `npm run lint` - ensure zero warnings
69-
- [ ] 9.3 Run `npm test` - ensure all tests pass
70-
- [ ] 9.4 Run `npm run build` - ensure successful build
68+
- [x] 9.1 Run `npm run typecheck` - ensure zero errors
69+
- [x] 9.2 Run `npm run lint` - ensure zero warnings (only pre-existing `any` warnings)
70+
- [x] 9.3 Run `npm test` - ensure all tests pass (322 passing, 47 skipped)
71+
- [x] 9.4 Run `npm run build` - ensure successful build
7172
- [ ] 9.5 Test examples manually against sandbox/production API

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)