npm package: @otempo/search
Description: Biblioteca Node.js
Author: Author
Latest version: 1.1.1

# 🗞️ O Tempo Search

Uma biblioteca Node.js moderna e tipada para integração com a API de pesquisa da O Tempo, oferecendo funcionalidades de busca com sugestões em tempo real.

## ✨ Características

- 🔍 **Pesquisa em tempo real** com sugestões automáticas
- 📄 **Paginação completa** com navegação entre páginas
- ⚡ **Debounce inteligente** para otimizar requisições
- 📊 **Estatísticas de pesquisa** com análises detalhadas
- 🏷️ **Filtros por categoria** (versal)
- 🔧 **Configuração flexível** com timeouts e headers customizados
- 📝 **TypeScript completo** com tipos bem definidos
- 🚀 **Performance otimizada** com axios
- 🛡️ **Tratamento de erros** robusto

## 📦 Instalação

### Via NPM (Recomendado)

```bash
npm install @otempo/search
```

### Via CDN (Navegador)

```html
<!-- Carregando via unpkg -->
<script src="https://unpkg.com/axios@1.6.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/@otempo/search@latest/dist/otempo-search.umd.min.js"></script>

<!-- Ou via jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@otempo/search@latest/dist/otempo-search.umd.min.js"></script>
```

## 🚀 Uso Rápido

### Pesquisa Básica

```javascript
const { search } = require('otempo-search');

// Pesquisa simples
const resultados = await search('bomba');
console.log(`Encontrados ${resultados.meta.count} resultados`);
```

### Com TypeScript

```typescript
import { search, SearchResponse } from '@otempo/search';

const resultados: SearchResponse = await search('guerra');
resultados.data.forEach(item => {
  console.log(`${item.titulo} - ${item.versal}`);
});
```

### Via CDN (Navegador)

```javascript
// Após carregar os scripts, a biblioteca fica disponível globalmente
const resultados = await window.OtempoSearch.search('tecnologia');
console.log(`Encontrados ${resultados.meta.count} resultados`);

// Usando funções helper
const sugestoes = await window.OtempoSearch.getSuggestions('politica', 5);

// Criando instância customizada
const cliente = new window.OtempoSearch.OtempoSearch({
  clientId: 'meu-app-123',
  searchIndex: 'news-index'
});
```

## 📚 Exemplos Detalhados

### 🔑 Client ID

A biblioteca suporta o envio de um Client ID em todas as requisições para identificação e rastreamento. O ID é enviado como header `X-Client-ID`.

```javascript
// Configurando clientId na instância
const cliente = new OtempoSearch({
  clientId: 'meu-app-123'
});

// Todas as requisições incluirão o clientId
const resultados = await cliente.search('tecnologia');

// Ou passando clientId por chamada específica
const resultados2 = await cliente.search('esportes', {
  clientId: 'app-esportes-456'
});

// Com funções helper
const sugestoes = await getSuggestions('politica', 5, {
  clientId: 'app-politica-789'
});
```

### 🔍 Search Index

A biblioteca suporta o envio de um Search Index como query parameter para especificar qual índice de pesquisa usar. O índice é enviado como parâmetro `searchIndex` na URL.

```javascript
// Configurando searchIndex na instância
const cliente = new OtempoSearch({
  searchIndex: 'news-index'
});

// Todas as requisições incluirão o searchIndex
const resultados = await cliente.search('tecnologia');

// Ou passando searchIndex por chamada específica
const resultados2 = await cliente.search('esportes', {
  searchIndex: 'sports-index'
});

// Com funções helper
const sugestoes = await getSuggestions('politica', 5, {
  searchIndex: 'politics-index'
});

// Combinando clientId e searchIndex
const clienteCompleto = new OtempoSearch({
  clientId: 'meu-app-123',
  searchIndex: 'main-index'
});
```

### 1. Pesquisa com Opções

```javascript
const { OtempoSearch } = require('otempo-search');

const cliente = new OtempoSearch({
  timeout: 10000,
  headers: {
    'X-Custom-Header': 'meu-app'
  }
});

const resultados = await cliente.search('política', {
  limit: 10
});
```

### 1.1. Pesquisa com Client ID

```javascript
const { OtempoSearch } = require('otempo-search');

// Configurando clientId na instância
const cliente = new OtempoSearch({
  clientId: 'meu-app-123',
  timeout: 10000
});

// Todas as requisições deste cliente incluirão o clientId
const resultados = await cliente.search('tecnologia');

// Ou passando clientId por chamada
const resultados2 = await cliente.search('esportes', {
  clientId: 'app-esportes-456',
  limit: 10
});
```

### 2. Sugestões em Tempo Real

```javascript
const { getSuggestions } = require('otempo-search');

// Busca 5 sugestões para "economia"
const sugestoes = await getSuggestions('economia', 5);
sugestoes.forEach(sugestao => {
  console.log(sugestao.titulo);
});

// Com clientId
const sugestoesComId = await getSuggestions('politica', 5, {
  clientId: 'app-politica-789'
});
```

### 3. Pesquisa com Debounce

```javascript
const { OtempoSearch } = require('otempo-search');

const cliente = new OtempoSearch();

// Simula digitação do usuário
cliente.searchWithDebounce('tecnologia', (response) => {
  console.log(`📊 ${response.meta.totalResults} resultados encontrados`);
  console.log(`📄 Página ${response.meta.page} de ${response.meta.totalPages}`);
  
  // Acessa os resultados
  response.data.forEach(item => {
    console.log(`- ${item.titulo} (${item.versal})`);
  });
  
  // Verifica paginação
  if (response.meta.hasNextPage) {
    console.log('⏭️ Tem próxima página');
  }
}, 300); // 300ms de delay
```

### 4. Filtros por Categoria

```javascript
const { OtempoSearch } = require('otempo-search');

const cliente = new OtempoSearch();

// Busca apenas notícias de esporte
const noticiasEsporte = await cliente.searchByCategory('ESPORTE');
```

### 5. Estatísticas da Pesquisa

```javascript
const { OtempoSearch } = require('otempo-search');

const cliente = new OtempoSearch();

const stats = await cliente.getSearchStats('cultura');
console.log(`Total: ${stats.totalResults}`);
console.log(`Média de caracteres: ${stats.averageTitleLength}`);
console.log('Categorias:', stats.categories);
```

### 6. Paginação

```javascript
const { search, getNextPage, getPage, getPaginationInfo } = require('otempo-search');

// Pesquisa com paginação
const resultado = await search('hugo', { limit: 5, page: 1 });
console.log(`Página ${resultado.meta.page} de ${resultado.meta.totalPages}`);

// Buscar próxima página
if (resultado.meta.hasNextPage) {
  const proximaPagina = await getNextPage('hugo', resultado.meta.page, { limit: 5 });
  console.log(`Próxima página: ${proximaPagina.data.length} resultados`);
}

// Buscar página específica
const pagina2 = await getPage('hugo', 2, { limit: 5 });

// Obter informações de paginação
const info = await getPaginationInfo('hugo', { limit: 10 });
console.log(`Total: ${info.totalResults}, Páginas: ${info.totalPages}`);
```

## 🔧 API Reference

### Classe `OtempoSearch`

#### Construtor

```typescript
new OtempoSearch(config?: OtempoSearchConfig)
```

**Configurações:**
- `baseURL?: string` - URL base da API (padrão: `https://searchapi.otempo.com.br/api`)
- `timeout?: number` - Timeout em ms (padrão: `5000`)
- `headers?: Record<string, string>` - Headers customizados
- `showSuggestions?: boolean` - Mostrar dropdown de sugestões por padrão (padrão: `true`)
- `showSuggestionsInResults?: boolean` - Mostrar sugestões na lista por padrão (padrão: `false`)
- `clientId?: string` - ID do cliente para incluir em todas as requisições (enviado como header `X-Client-ID`)
- `searchIndex?: string` - Índice de pesquisa para incluir como query parameter

#### Métodos

##### `search(query: string, options?: SearchOptions): Promise<SearchResponse>`

Realiza uma pesquisa na API.

**Parâmetros:**
- `query: string` - Termo de pesquisa (mínimo 2 caracteres)
- `options?: SearchOptions` - Opções da pesquisa

**Opções:**
- `limit?: number` - Número máximo de resultados
- `page?: number` - Número da página (padrão: 1)
- `timeout?: number` - Timeout específico para esta requisição
- `headers?: Record<string, string>` - Headers específicos
- `showSuggestions?: boolean` - Mostrar dropdown de sugestões (padrão: true)
- `showSuggestionsInResults?: boolean` - Mostrar sugestões na lista de resultados (padrão: false)
- `clientId?: string` - ID do cliente para incluir nesta requisição (enviado como header `X-Client-ID`)
- `searchIndex?: string` - Índice de pesquisa para incluir como query parameter

**Observações:**
- A query deve ter pelo menos 2 caracteres para otimizar o uso da API

##### `searchWithDebounce(query: string, callback: Function, delay?: number, options?: SearchOptions): void`

Realiza pesquisa com debounce para sugestões em tempo real.

**Parâmetros:**
- `query: string` - Termo de pesquisa
- `callback: (response: SearchResponse) => void` - Função de callback com resposta completa (incluindo metadados de paginação)
- `delay?: number` - Delay em ms (padrão: `300`)
- `options?: SearchOptions` - Opções da pesquisa

**Observações:**
- O callback recebe a resposta completa da API, incluindo metadados de paginação
- Para acessar apenas os resultados, use `response.data`
- Para acessar os metadados, use `response.meta`

##### `getSuggestions(query: string, maxSuggestions?: number, options?: SearchOptions): Promise<SearchResult[]>`

Busca sugestões para um termo.

**Parâmetros:**
- `query: string` - Termo de pesquisa (mínimo 2 caracteres)
- `maxSuggestions?: number` - Máximo de sugestões (padrão: `5`)
- `options?: SearchOptions` - Opções da pesquisa

##### `searchByCategory(versal: string, options?: SearchOptions): Promise<SearchResult[]>`

Busca notícias por categoria específica.

##### `getSearchStats(query: string, options?: SearchOptions): Promise<SearchStats>`

Obtém estatísticas da pesquisa.

##### `cancelSearch(): void`

Cancela a pesquisa com debounce pendente.

##### `getNextPage(query: string, currentPage: number, options?: SearchOptions): Promise<SearchResponse>`

Busca a próxima página de resultados.

**Parâmetros:**
- `query: string` - Termo de pesquisa original
- `currentPage: number` - Página atual
- `options?: SearchOptions` - Opções da pesquisa

##### `getPrevPage(query: string, currentPage: number, options?: SearchOptions): Promise<SearchResponse>`

Busca a página anterior de resultados.

**Parâmetros:**
- `query: string` - Termo de pesquisa original
- `currentPage: number` - Página atual
- `options?: SearchOptions` - Opções da pesquisa

**Observações:**
- Lança erro se tentar buscar página anterior à primeira

##### `getPage(query: string, page: number, options?: SearchOptions): Promise<SearchResponse>`

Busca uma página específica de resultados.

**Parâmetros:**
- `query: string` - Termo de pesquisa
- `page: number` - Número da página
- `options?: SearchOptions` - Opções da pesquisa

**Observações:**
- Lança erro se o número da página for menor que 1

##### `getAllPages(query: string, options?: SearchOptions): Promise<SearchResult[]>`

Obtém todas as páginas de resultados para uma pesquisa.

**Parâmetros:**
- `query: string` - Termo de pesquisa
- `options?: SearchOptions` - Opções da pesquisa

**Observações:**
- Busca automaticamente todas as páginas disponíveis
- Inclui limite de segurança de 100 páginas para evitar loops infinitos

##### `getPaginationInfo(query: string, options?: SearchOptions): Promise<PaginationInfo>`

Obtém informações de paginação para uma pesquisa.

**Parâmetros:**
- `query: string` - Termo de pesquisa
- `options?: SearchOptions` - Opções da pesquisa

**Retorna:**
```typescript
{
  currentPage: number;
  totalPages: number;
  totalResults: number;
  hasNextPage: boolean;
  hasPrevPage: boolean;
  resultsPerPage: number;
}
```

### Funções Helper

#### `search(query: string, options?: SearchOptions): Promise<SearchResponse>`

Função helper para pesquisa rápida usando instância padrão.

#### `getSuggestions(query: string, maxSuggestions?: number, options?: SearchOptions): Promise<SearchResult[]>`

Função helper para sugestões rápidas.

#### `getNextPage(query: string, currentPage: number, options?: SearchOptions): Promise<SearchResponse>`

Função helper para buscar próxima página.

#### `getPrevPage(query: string, currentPage: number, options?: SearchOptions): Promise<SearchResponse>`

Função helper para buscar página anterior.

#### `getPage(query: string, page: number, options?: SearchOptions): Promise<SearchResponse>`

Função helper para buscar página específica.

#### `getAllPages(query: string, options?: SearchOptions): Promise<SearchResult[]>`

Função helper para obter todas as páginas.

#### `getPaginationInfo(query: string, options?: SearchOptions): Promise<PaginationInfo>`

Função helper para obter informações de paginação.

## 📊 Tipos TypeScript

### `SearchResult`

```typescript
interface SearchResult {
  titulo: string;      // Título da notícia
  versal: string;      // Categoria/versal
  bigode: string;      // Resumo/descrição
  imagem: string;      // URL da imagem
  path: string;        // Caminho/URL da notícia
}
```

### `SearchResponse`

```typescript
interface SearchResponse {
  success: boolean;           // Sucesso da requisição
  data: SearchResult[];       // Array de resultados
  meta: SearchMeta;          // Metadados da pesquisa
}
```

### `SearchMeta`

```typescript
interface SearchMeta {
  query: string;              // Query executada
  count: number;              // Total de resultados
  limit: number;              // Limite por página
  page: number;               // Página atual
  totalPages: number;         // Número total de páginas
  totalResults: number;       // Número total de resultados
  hasNextPage: boolean;       // Indica se existe próxima página
  hasPrevPage: boolean;       // Indica se existe página anterior
  timestamp: string;          // Timestamp da resposta
}
```

## 🛠️ Desenvolvimento

### Instalação das Dependências

```bash
npm install
```

### Compilação

```bash
npm run build
```

### Desenvolvimento com Watch

```bash
npm run dev
```

### Testes

```bash
npm test
```

### Linting

```bash
npm run lint
```

## 📦 Publicação

### Configuração do Azure Artifacts

Este pacote é publicado no Azure Artifacts. Para configurar a autenticação localmente:

1. **Gerar um Personal Access Token (PAT)** com permissões de Packaging (Read & Write)
2. **Configurar a variável de ambiente:**
   ```bash
   export AZURE_PAT='seu_pat_aqui'
   ```

3. **Testar a autenticação:**
   ```bash
   ./scripts/test-auth.sh
   ```

4. **Publicar o pacote:**
   ```bash
   npm run publish:azure
   ```

### Pipeline de CI/CD

O projeto usa Azure DevOps Pipeline para publicação automática. O pipeline:
- Executa testes e linting
- Compila o código TypeScript
- Publica automaticamente no Azure Artifacts quando há commits na branch `master`

### Estrutura do Feed

- **Registry:** `https://pkgs.dev.azure.com/gruposada/0ac0f368-01b8-4a03-8223-f071ef20141a/_packaging/Otempo-Search/npm/registry/`
- **Escopo:** `@otempo`
- **Nome do pacote:** `@otempo/search`

## 🎨 Frontend de Teste

Para testar a biblioteca de forma interativa, você pode usar o frontend incluído:

### Iniciar o Frontend

```bash
# Usando npm script (recomendado)
npm run frontend

# Ou navegando para a pasta
cd frontend && node server.js
```

### Acessar o Frontend

Abra seu navegador e acesse: `http://localhost:3000`

### Funcionalidades do Frontend

- 🔍 **Pesquisa em tempo real** com sugestões automáticas
- 💡 **Sugestões inteligentes** que aparecem enquanto você digita
- 📊 **Estatísticas detalhadas** dos resultados da pesquisa
- 🎨 **Interface moderna** e responsiva
- ⚡ **Performance otimizada** com debounce

Para mais detalhes, consulte o [README do Frontend](frontend/README.md).

## 📁 Estrutura do Projeto

```
otempo_search/
├── src/
│   ├── types/
│   │   └── index.ts          # Definições de tipos
│   ├── OtempoSearch.ts       # Classe principal
│   └── index.ts              # Exports principais
├── examples/
│   ├── basic-usage.js        # Exemplos JavaScript
│   └── typescript-usage.ts   # Exemplos TypeScript
├── frontend/                 # Frontend de teste
│   ├── index.html            # Página principal
│   ├── app.js                # Lógica da aplicação
│   ├── server.js             # Servidor Node.js
│   └── README.md             # Documentação do frontend
├── tests/                    # Testes unitários
│   ├── setup.ts              # Configuração dos testes
│   └── OtempoSearch.test.ts  # Testes da biblioteca
├── dist/                     # Código compilado
├── package.json
├── tsconfig.json
└── README.md
```

## 🌐 Uso via CDN

A biblioteca está disponível via CDN para uso direto no navegador, sem necessidade de instalação via NPM.

### URLs dos CDNs

**unpkg:**
- `https://unpkg.com/@otempo/search@latest/dist/otempo-search.umd.min.js`

**jsdelivr:**
- `https://cdn.jsdelivr.net/npm/@otempo/search@latest/dist/otempo-search.umd.min.js`

### Exemplo Completo

```html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>O Tempo Search - Exemplo CDN</title>
</head>
<body>
    <h1>🗞️ O Tempo Search</h1>
    <input type="text" id="searchInput" placeholder="Digite sua pesquisa...">
    <button onclick="search()">🔍 Pesquisar</button>
    <div id="results"></div>

    <!-- Carregando a biblioteca via CDN -->
    <script src="https://unpkg.com/axios@1.6.0/dist/axios.min.js"></script>
    <script src="https://unpkg.com/@otempo/search@latest/dist/otempo-search.umd.min.js"></script>
    
    <script>
        async function search() {
            const query = document.getElementById('searchInput').value;
            try {
                const results = await window.OtempoSearch.search(query);
                document.getElementById('results').innerHTML = 
                    `<p>Encontrados ${results.meta.count} resultados</p>`;
            } catch (error) {
                console.error('Erro:', error);
            }
        }
    </script>
</body>
</html>
```

### Funcionalidades Disponíveis via CDN

- ✅ **Pesquisa básica**: `window.OtempoSearch.search(query, options)`
- ✅ **Sugestões**: `window.OtempoSearch.getSuggestions(query, maxSuggestions, options)`
- ✅ **Paginação**: `window.OtempoSearch.getPage(query, page, options)`
- ✅ **Instância customizada**: `new window.OtempoSearch.OtempoSearch(config)`
- ✅ **Todas as funções helper** disponíveis no objeto global

### Exemplos de Uso via CDN

```javascript
// Pesquisa básica
const results = await window.OtempoSearch.search('tecnologia');

// Com configurações
const results = await window.OtempoSearch.search('esportes', {
    limit: 10,
    clientId: 'meu-app-123',
    searchIndex: 'news-index'
});

// Sugestões
const suggestions = await window.OtempoSearch.getSuggestions('politica', 5);

// Instância customizada
const client = new window.OtempoSearch.OtempoSearch({
    clientId: 'custom-app',
    timeout: 10000
});
```

### Exemplos Prontos

- 📄 [Exemplo HTML completo](examples/cdn-example.html)
- 📄 [Exemplos JavaScript](examples/cdn-usage.js)

## 🔗 Endpoint da API

A biblioteca consome o endpoint:
```
https://searchapi.otempo.com.br/api/search?q={query}
```

**Resposta esperada:**
```json
{
  "success": true,
  "data": [
    {
      "titulo": "Título da notícia",
      "versal": "CATEGORIA",
      "bigode": "Resumo da notícia",
      "imagem": "/path/to/image.jpg",
      "path": "/path/to/news"
    }
  ],
  "meta": {
    "query": "termo pesquisado",
    "count": 1,
    "limit": 20,
    "page": 1,
    "totalPages": 1,
    "totalResults": 1,
    "hasNextPage": false,
    "hasPrevPage": false,
    "timestamp": "2025-07-02T11:52:18.510Z"
  }
}
```

## 🤝 Contribuindo

1. Fork o projeto
2. Crie uma branch para sua feature (`git checkout -b feature/AmazingFeature`)
3. Commit suas mudanças (`git commit -m 'Add some AmazingFeature'`)
4. Push para a branch (`git push origin feature/AmazingFeature`)
5. Abra um Pull Request

## 📄 Licença

Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.

## 🆘 Suporte

Se você encontrar algum problema ou tiver dúvidas, por favor abra uma [issue](https://github.com/yourusername/otempo-search/issues).

---

Desenvolvido com ❤️ para a comunidade de desenvolvedores brasileiros.