# KV Store

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

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

KV Store is a distributed key-value storage service that runs across Azion's 
network. It lets you persist and retrieve small pieces of data with very 
low latency from anywhere your users are, without managing servers.

The JavaScript API is designed to be compatible with the Cloudflare Workers KV 
API, making it easier to migrate existing applications to Azion's platform.

Typical use cases include:

- Session and authentication tokens
- Feature flags and A/B testing configurations
- User preferences and personalization
- Caching API responses and computed fragments
- Rate-limit counters and idempotency keys
- Shopping cart or draft state

---

## How it works

KV Store organizes data into namespaces, where each namespace contains an 
independent set of keys. Here's how the system operates:

- Data is organized into **namespaces** that contain independent sets of keys.
- Each item is addressed by a **key** that is unique within its namespace.
- Values can be stored as **text**, **JSON**, **ArrayBuffer**, or **ReadableStream**.
- Data is replicated across global points of presence to maximize availability and read performance.
- Single-key operations are atomic per key. Multi-key transactions aren't supported.

---

## Implementation resources

| Scope | Resource |
| ----- | -------- |
| Manage KV Store with Functions | [How to manage KV Store with Functions](/en/documentation/products/guides/kv-store/manage-with-functions/) |
| KV Store API reference | [Azion API - KV Store](https://api.azion.com/) |

---

## Data resilience

KV Store uses a distributed architecture with replication across Azion 
nodes. New writes are accepted and propagated to replicas to ensure 
durability and high availability. Reads are served from the closest healthy 
replica to minimize latency.

---

## Namespaces

A namespace is an isolated key space. Use namespaces to segment data by 
application, environment, or workload.

### Recommended patterns

- Use separate namespaces for production and staging environments.
- Prefix keys to model hierarchy, for example: `users/123/profile`, `flags/new-ui`, `carts/region-br/user-42`.
- Keep keys short and meaningful; prefer a few path-like segments over long opaque identifiers.

### Naming conventions

- Names must be unique within your account.
- Names must be between 3 and 63 characters.
- Use lowercase letters, numbers, dashes, and underscores.
- Names must match the pattern: `^[a-zA-Z0-9_-]+$`

### Managing namespaces via API

You can manage namespaces using the Azion API. The base endpoint is:

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

#### Create a namespace

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

**Response (201 Created):**

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

#### List namespaces

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

**Query parameters:**

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `fields` | string | Comma-separated list of field names to include in the response |
| `page` | integer | Page number within the paginated result set |
| `page_size` | integer | Number of items per page |

#### Retrieve a namespace

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

---

## Interacting with KV Store via Functions

You can interact with KV Store directly from your functions using the 
`Azion.KV` class. The examples below illustrate common patterns for creating, 
reading, updating, and deleting data.

### Initializing the KV client

You can initialize the KV client using the constructor or the `open` method:

```javascript
// Using the default namespace
const kv = new Azion.KV();

// Using a specific namespace
const kv = new Azion.KV("my-namespace");

// Using the open method
const kv = await Azion.KV.open("my-namespace");
```

### Storing data (put)

Store values in different formats:

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

    // Store a string value
    await kv.put(key, "session-data-here");

    // Store a JSON object
    await kv.put(key, { userId: 123, role: "admin" });

    // Store an ArrayBuffer
    const encoder = new TextEncoder();
    const buffer = encoder.encode("binary data").buffer;
    await kv.put(key, buffer);

    // Store with options (metadata and expiration)
    await kv.put(key, "value", {
        metadata: { created_by: "user-42" },
        expiration: Math.floor(Date.now() / 1000) + 3600, // expires in 1 hour
        expirationTtl: 3600 // alternative: TTL in seconds
    });

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

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

### Storing large data with streams

For large payloads, use `ReadableStream` to stream data efficiently:

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

    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("Large data stored", { status: 200 });
}

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

### Retrieving data (get)

Retrieve values in different formats:

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

    // Get as text (default)
    const textValue = await kv.get(key, "text");

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

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

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

    // Handle non-existent keys (returns null)
    const missing = await kv.get("non-existent-key", "text");
    if (missing === null) {
        console.log("Key not found");
    }

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

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

### Retrieving multiple keys

Retrieve multiple values in a single operation:

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

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

    // Returns an object with key-value pairs
    // Non-existent keys have null values
    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));
});
```

### Retrieving data with metadata

Use `getWithMetadata` to retrieve both the value and its associated metadata:

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

    // Store with metadata
    await kv.put(key, "session-value", {
        metadata: { created_by: "user-42", version: 1 }
    });

    // Retrieve with metadata
    const data = await kv.getWithMetadata(key, "text");
    console.log(`Value: ${data.value}`);
    console.log(`Metadata: ${JSON.stringify(data.metadata)}`);

    // Works with multiple keys too
    const keys = ["key1", "key2"];
    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));
});
```

### Reading streams

Process streamed data incrementally:

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

    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(`Retrieved ${text.length} characters`);
    }

    // Or return the stream directly in a response
    return new Response(stream);
}

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

### Deleting data

Remove a key from the store:

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

    await kv.delete(key);

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

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

### Working with Unicode and special characters

KV Store supports UTF-8 and UTF-16 encoded keys and values:

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

    // UTF-16 key (Chinese: "My Key")
    const key = "我的钥匙";

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

    await kv.put(key, value);

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

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

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

### Deleting namespaces

Delete an entire namespace programmatically:

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

    // Delete the namespace
    await Azion.KV.delete(namespaceName);

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

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

---

## Methods reference

The `Azion.KV` class provides the following methods:

### Constructor and initialization

| Method | Description |
| ------ | ----------- |
| `new Azion.KV()` | Creates a KV instance using the default namespace |
| `new Azion.KV(name)` | Creates a KV instance for the specified namespace |
| `Azion.KV.open(name)` | Opens a namespace asynchronously |
| `Azion.KV.delete(name)` | Deletes a namespace |

### Data operations

| Method | Parameters | Returns | Description |
| ------ | ---------- | ------- | ----------- |
| `put(key, value, options?)` | `key`: string<br />`value`: string \| object \| ArrayBuffer \| ReadableStream<br />`options`: `{ metadata?, expiration?, expirationTtl? }` | `Promise<void>` | Stores a value |
| `get(key, type?, options?)` | `key`: string \| string[]<br />`type`: "text" \| "json" \| "arrayBuffer" \| "stream"<br />`options`: `{ cacheTtl? }` | `Promise<value \| null>` | Retrieves a value |
| `getWithMetadata(key, type?, options?)` | `key`: string \| string[]<br />`type`: "text" \| "json" \| "arrayBuffer" \| "stream"<br />`options`: `{ cacheTtl? }` | `Promise<{'{value, metadata}'}>` | Retrieves value with metadata |
| `delete(key)` | `key`: string | `Promise<void>` | Deletes a key |

### Put options

| Option | Type | Description |
| ------ | ---- | ----------- |
| `metadata` | object | Custom metadata to associate with the key (max 1024 bytes JSON-serialized) |
| `expiration` | number | Unix timestamp (seconds) when the key expires |
| `expirationTtl` | number | Time-to-live in seconds from now |

### Get options

| Option | Type | Description |
| ------ | ---- | ----------- |
| `cacheTtl` | number | Time in seconds to cache the result locally (minimum 60 seconds) |

### Get types

| Type | Returns | Description |
| ---- | ------- | ----------- |
| `"text"` | string | Returns the value as a UTF-8 string (default) |
| `"json"` | object | Parses the value as JSON |
| `"arrayBuffer"` | ArrayBuffer | Returns raw binary data |
| `"stream"` | ReadableStream | Returns a readable stream for large values |

---

## Limits

These are the **default limits**:

| Limit | Value |
| ----- | ----- |
| Per-key write rate | Up to 1 write per second to the same key |
| Key size | Up to 512 bytes (UTF-8) |
| Metadata size | Up to 1024 bytes (JSON-serialized) |
| Value size | Up to 25 MB per item |
| Namespace name length | 3-63 characters |
| Minimum `expirationTtl` | 60 seconds |
| Minimum `cacheTtl` | 60 seconds |

These are the **default limits** for each Service Plan:

| Scope | Developer | Business | Enterprise | Mission Critical |
| ----- | --------- | -------- | ---------- | ---------------- |
| Namespaces | 1000 | 1000 | 1000 | 1000 |
| Maximum file size | 200 MB | 500 MB | 2 GB | 2 GB |
| Maximum storage per account | 5 GB | 50 GB | 300 GB | 300 GB |

---

## Limitations

The following operations are **not supported**:

- **List keys**: There is no `list()` method to enumerate keys within a namespace. Design your application to track keys externally if needed.
- **Multi-key transactions**: Operations are atomic per key, but there is no support for transactions spanning multiple keys.
- **Namespace management from functions**: Namespaces must be created and managed via the [Azion API](https://api.azion.com/) or Console, not from within functions.

---