# KV Store

import Tag from '~/components/webkit/Tag.vue'
import DocButton from '~/components/webkit/DocButton.vue';

<Tag severity="info">
  Preview
</Tag>

KV Store é um serviço de armazenamento de chave-valor distribuído que opera em 
toda a rede da Azion. Ele permite que você persista e recupere pequenos 
pedaços de dados com latência muito baixa de qualquer lugar onde seus usuários 
estejam, sem precisar gerenciar servidores.

A API JavaScript foi projetada para ser compatível com a API do Cloudflare Workers 
KV, facilitando a migração de aplicações existentes para a plataforma da Azion.

Casos de uso típicos incluem:

- Sessões e tokens de autenticação
- Feature flags e configurações de testes A/B
- Preferências de usuário e personalização
- Cache de respostas de API e fragmentos computados
- Contadores de rate limit e chaves de idempotência
- Estado do carrinho de compras ou rascunho

---

## Como funciona

O KV Store organiza dados em namespaces, onde cada namespace contém um conjunto 
independente de chaves. Veja como o sistema opera:

- Os dados são organizados em **namespaces** que contêm conjuntos independentes de chaves.
- Cada item é endereçado por uma **chave** que é única dentro de seu namespace.
- Os valores podem ser armazenados como **texto**, **JSON**, **ArrayBuffer** ou **ReadableStream**.
- Os dados são replicados entre os pontos de presença globais para maximizar a disponibilidade e o desempenho de leitura.
- Operações de chave única são atômicas por chave. Transações de múltiplas chaves não são suportadas.

---

## Recursos de implementação

| Escopo | Recurso |
| ------ | ------- |
| Gerenciar KV Store com Functions | [Como gerenciar KV Store com Functions](/pt-br/documentacao/produtos/guias/kv-store/gerenciar-com-funcoes/) |
| Referência da API KV Store | [Azion API - KV Store](https://api.azion.com/) |

---

## Resiliência de dados

O KV Store usa uma arquitetura distribuída com replicação entre os nodes 
da Azion. Novas gravações são aceitas e propagadas para réplicas para 
garantir durabilidade e alta disponibilidade. Leituras são servidas da réplica 
saudável mais próxima para minimizar a latência.

---

## Namespaces

Um namespace é um espaço de chaves isolado. Use namespaces para segmentar dados 
por aplicação, ambiente ou carga de trabalho.

### Padrões recomendados

- Use namespaces separados para ambientes de produção e staging.
- Prefixe chaves para modelar hierarquia, por exemplo: `users/123/profile`, `flags/new-ui`, `carts/region-br/user-42`.
- Mantenha as chaves curtas e significativas; prefira alguns segmentos do tipo caminho em vez de longos identificadores opacos.

### Convenções de nomenclatura

- Os nomes devem ser únicos dentro da sua conta.
- Os nomes devem ter entre 3 e 63 caracteres.
- Use letras minúsculas, números, hifens e underscores.
- Os nomes devem corresponder ao padrão: `^[a-zA-Z0-9_-]+$`

### Gerenciando namespaces via API

Você pode gerenciar namespaces usando a API da Azion. O endpoint base é:

```
https://api.azion.com/v4/workspace/kv/namespaces
```

#### Criar um namespace

```bash
curl -X POST "https://api.azion.com/v4/workspace/kv/namespaces" \
  -H "Authorization: Token SEU_TOKEN_API" \
  -H "Content-Type: application/json" \
  -d '{"name": "meu-namespace"}'
```

**Resposta (201 Created):**

```json
{
  "name": "meu-namespace",
  "created_at": "2025-01-24T14:15:22Z",
  "last_modified": "2025-01-24T14:15:22Z"
}
```

#### Listar namespaces

```bash
curl -X GET "https://api.azion.com/v4/workspace/kv/namespaces" \
  -H "Authorization: Token SEU_TOKEN_API" \
  -H "Accept: application/json"
```

**Parâmetros de consulta:**

| Parâmetro | Tipo | Descrição |
| --------- | ---- | --------- |
| `fields` | string | Lista separada por vírgulas de nomes de campos para incluir na resposta |
| `page` | integer | Número da página dentro do conjunto de resultados paginados |
| `page_size` | integer | Número de itens por página |

#### Recuperar um namespace

```bash
curl -X GET "https://api.azion.com/v4/workspace/kv/namespaces/{namespace}" \
  -H "Authorization: Token SEU_TOKEN_API" \
  -H "Accept: application/json"
```

---

## Interação com KV Store via Functions

Você pode interagir com o KV Store diretamente de suas Functions usando a 
classe `Azion.KV`. Os exemplos abaixo ilustram padrões comuns para criar, ler, 
atualizar e excluir dados.

### Inicializando o cliente KV

Você pode inicializar o cliente KV usando o construtor ou o método `open`:

```javascript
// Usando o namespace padrão
const kv = new Azion.KV();

// Usando um namespace específico
const kv = new Azion.KV("meu-namespace");

// Usando o método open
const kv = await Azion.KV.open("meu-namespace");
```

### Armazenando dados (put)

Armazene valores em diferentes formatos:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();
    const key = "sessao-usuario";

    // Armazenar um valor string
    await kv.put(key, "dados-da-sessao-aqui");

    // Armazenar um objeto JSON
    await kv.put(key, { userId: 123, role: "admin" });

    // Armazenar um ArrayBuffer
    const encoder = new TextEncoder();
    const buffer = encoder.encode("dados binários").buffer;
    await kv.put(key, buffer);

    // Armazenar com opções (metadados e expiração)
    await kv.put(key, "valor", {
        metadata: { created_by: "user-42" },
        expiration: Math.floor(Date.now() / 1000) + 3600, // expira em 1 hora
        expirationTtl: 3600 // alternativa: TTL em segundos
    });

    return new Response("Dados armazenados", { status: 200 });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Armazenando dados grandes com streams

Para payloads grandes, use `ReadableStream` para transmitir dados de forma eficiente:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();
    const key = "arquivo-grande";
    const dataSizeInKB = 500;
    const totalSize = dataSizeInKB * 1024;
    const chunkSize = 64 * 1024; // chunks de 64KB

    const stream = new ReadableStream({
        async start(controller) {
            const encoder = new TextEncoder();
            const numChunks = Math.ceil(totalSize / chunkSize);

            for (let i = 0; i < numChunks; i++) {
                const currentChunkSize = Math.min(chunkSize, totalSize - i * chunkSize);
                const chunk = "a".repeat(currentChunkSize);
                controller.enqueue(encoder.encode(chunk));
            }
            controller.close();
        },
    });

    await kv.put(key, stream);

    return new Response("Dados grandes armazenados", { status: 200 });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Recuperando dados (get)

Recupere valores em diferentes formatos:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();
    const key = "sessao-usuario";

    // Obter como texto (padrão)
    const textValue = await kv.get(key, "text");

    // Obter como JSON
    const jsonValue = await kv.get(key, "json");
    console.log(`User ID: ${jsonValue.userId}`);

    // Obter como ArrayBuffer
    const bufferValue = await kv.get(key, "arrayBuffer");
    const decoder = new TextDecoder("utf-8");
    const decodedString = decoder.decode(bufferValue);

    // Obter como ReadableStream
    const streamValue = await kv.get(key, "stream");

    // Tratar chaves inexistentes (retorna null)
    const missing = await kv.get("chave-inexistente", "text");
    if (missing === null) {
        console.log("Chave não encontrada");
    }

    return new Response(textValue, { status: 200 });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Recuperando múltiplas chaves

Recupere múltiplos valores em uma única operação:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();

    const keys = ["usuario-1", "usuario-2", "usuario-3"];
    const data = await kv.get(keys, "text");

    // Retorna um objeto com pares chave-valor
    // Chaves inexistentes têm valores null
    console.log(JSON.stringify(data));

    return new Response(JSON.stringify(data), {
        headers: { "Content-Type": "application/json" },
        status: 200,
    });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Recuperando dados com metadados

Use `getWithMetadata` para recuperar tanto o valor quanto seus metadados associados:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();
    const key = "sessao-usuario";

    // Armazenar com metadados
    await kv.put(key, "valor-sessao", {
        metadata: { created_by: "user-42", version: 1 }
    });

    // Recuperar com metadados
    const data = await kv.getWithMetadata(key, "text");
    console.log(`Valor: ${data.value}`);
    console.log(`Metadados: ${JSON.stringify(data.metadata)}`);

    // Funciona com múltiplas chaves também
    const keys = ["chave1", "chave2"];
    const multiData = await kv.getWithMetadata(keys, "text");

    return new Response(JSON.stringify(data), {
        headers: { "Content-Type": "application/json" },
        status: 200,
    });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Lendo streams

Processe dados em stream de forma incremental:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();
    const key = "arquivo-grande";

    const stream = await kv.get(key, "stream");

    if (stream instanceof ReadableStream) {
        const decoder = new TextDecoder();
        let text = "";

        for await (const chunk of stream) {
            text += decoder.decode(chunk, { stream: true });
        }
        text += decoder.decode();

        console.log(`Recuperados ${text.length} caracteres`);
    }

    // Ou retorne o stream diretamente na resposta
    return new Response(stream);
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Excluindo dados

Remova uma chave do armazenamento:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();
    const key = "sessao-usuario";

    await kv.delete(key);

    return new Response("Chave excluída", { status: 200 });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Trabalhando com Unicode e caracteres especiais

O KV Store suporta chaves e valores codificados em UTF-8 e UTF-16:

```javascript
async function handleRequest(request) {
    const kv = new Azion.KV();

    // Chave UTF-16 (Chinês: "Minha Chave")
    const key = "我的钥匙";

    // Valor UTF-16 com emojis
    const value = "🌍🌎🌏 Hello World 你好 世界";

    await kv.put(key, value);

    const retrieved = await kv.get(key, "text");
    console.log(`Chave: ${key}, Valor: ${retrieved}`);

    return new Response("OK", { status: 200 });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

### Excluindo namespaces

Exclua um namespace inteiro programaticamente:

```javascript
async function handleRequest(request) {
    const namespaceName = "meu-namespace";

    // Excluir o namespace
    await Azion.KV.delete(namespaceName);

    return new Response("Namespace excluído", { status: 200 });
}

addEventListener("fetch", (event) => {
    event.respondWith(handleRequest(event.request));
});
```

---

## Referência de métodos

A classe `Azion.KV` fornece os seguintes métodos:

### Construtor e inicialização

| Método | Descrição |
| ------ | --------- |
| `new Azion.KV()` | Cria uma instância KV usando o namespace padrão |
| `new Azion.KV(name)` | Cria uma instância KV para o namespace especificado |
| `Azion.KV.open(name)` | Abre um namespace de forma assíncrona |
| `Azion.KV.delete(name)` | Exclui um namespace |

### Operações de dados

| Método | Parâmetros | Retorna | Descrição |
| ------ | ---------- | ------- | --------- |
| `put(key, value, options?)` | `key`: string<br />`value`: string \| object \| ArrayBuffer \| ReadableStream<br />`options`: `{ metadata?, expiration?, expirationTtl? }` | `Promise<void>` | Armazena um valor |
| `get(key, type?, options?)` | `key`: string \| string[]<br />`type`: "text" \| "json" \| "arrayBuffer" \| "stream"<br />`options`: `{ cacheTtl? }` | `Promise<value \| null>` | Recupera um valor |
| `getWithMetadata(key, type?, options?)` | `key`: string \| string[]<br />`type`: "text" \| "json" \| "arrayBuffer" \| "stream"<br />`options`: `{ cacheTtl? }` | `Promise<{'{value, metadata}'}>` | Recupera valor com metadados |
| `delete(key)` | `key`: string | `Promise<void>` | Exclui uma chave |

### Opções do put

| Opção | Tipo | Descrição |
| ----- | ---- | --------- |
| `metadata` | object | Metadados personalizados para associar à chave (máx 1024 bytes JSON-serializado) |
| `expiration` | number | Timestamp Unix (segundos) quando a chave expira |
| `expirationTtl` | number | Time-to-live em segundos a partir de agora |

### Opções do get

| Opção | Tipo | Descrição |
| ----- | ---- | --------- |
| `cacheTtl` | number | Tempo em segundos para armazenar o resultado em cache localmente (mínimo 60 segundos) |

### Tipos do get

| Tipo | Retorna | Descrição |
| ---- | ------- | --------- |
| `"text"` | string | Retorna o valor como uma string UTF-8 (padrão) |
| `"json"` | object | Faz parse do valor como JSON |
| `"arrayBuffer"` | ArrayBuffer | Retorna dados binários brutos |
| `"stream"` | ReadableStream | Retorna um stream legível para valores grandes |

---

## Limites

Estes são os **limites padrão**:

| Limite | Valor |
| ------ | ----- |
| Taxa de gravação por chave | Até 1 gravação por segundo para a mesma chave |
| Tamanho da chave | Até 512 bytes (UTF-8) |
| Tamanho dos metadados | Até 1024 bytes (JSON-serializado) |
| Tamanho do valor | Até 25 MB por item |
| Comprimento do nome do namespace | 3-63 caracteres |
| Mínimo `expirationTtl` | 60 segundos |
| Mínimo `cacheTtl` | 60 segundos |

Estes são os **limites padrão** para cada Plano de Serviço:

| Escopo | Developer | Business | Enterprise | Mission Critical |
| ------ | --------- | -------- | ---------- | ---------------- |
| Namespaces | 1000 | 1000 | 1000 | 1000 |
| Tamanho máximo do arquivo | 200 MB | 500 MB | 2 GB | 2 GB |
| Armazenamento máximo por conta | 5 GB | 50 GB | 300 GB | 300 GB |

---

## Limitações

As seguintes operações **não são suportadas**:

- **Listar chaves**: Não existe um método `list()` para enumerar chaves dentro de um namespace. Projete sua aplicação para rastrear chaves externamente, se necessário.
- **Transações de múltiplas chaves**: Operações são atômicas por chave, mas não há suporte para transações que abrangem múltiplas chaves.
- **Gerenciamento de namespaces a partir de functions**: Namespaces devem ser criados e gerenciados via [API da Azion](https://api.azion.com/) ou Console, não de dentro de functions.

---