npm package: @farfarawaylabs/chat-sdk-adapter-kv
Description: Cloudflare KV state adapter for the Vercel Chat SDK
Latest version: 0.2.0

# @farfarawaylabs/chat-sdk-adapter-kv

Cloudflare KV state adapter for the [Vercel Chat SDK](https://www.npmjs.com/package/chat). A drop-in replacement for the Redis state adapter, designed for Cloudflare Workers.

## Installation

```bash
npm install @farfarawaylabs/chat-sdk-adapter-kv
```

## Usage

Pass your KV namespace binding to `createCloudflareKVState()`:

```ts
import { Chat } from "chat";
import { createCloudflareKVState } from "@farfarawaylabs/chat-sdk-adapter-kv";

export default {
  async fetch(request: Request, env: Env) {
    const bot = new Chat({
      userName: "mybot",
      adapters: { /* ... */ },
      state: createCloudflareKVState({ kv: env.CHAT_KV }),
    });
  },
};
```

### Wrangler configuration

Add a KV namespace binding in your `wrangler.toml`:

```toml
[[kv_namespaces]]
binding = "CHAT_KV"
id = "your-kv-namespace-id"
```

### Key prefix

All keys are namespaced under a configurable prefix (default: `"chat-sdk"`):

```ts
const state = createCloudflareKVState({
  kv: env.CHAT_KV,
  keyPrefix: "my-bot",
});
```

### Custom logger

```ts
const state = createCloudflareKVState({
  kv: env.CHAT_KV,
  logger: myLogger,
});
```

## Configuration

| Option      | Required | Description                                    |
| ----------- | -------- | ---------------------------------------------- |
| `kv`        | Yes      | Cloudflare KV namespace binding                |
| `keyPrefix` | No       | Prefix for all keys (default: `"chat-sdk"`)    |
| `logger`    | No       | Logger instance (defaults to `ConsoleLogger`)   |

## Key structure

```
{keyPrefix}:sub:{threadId}    - Subscription marker for a thread
{keyPrefix}:lock:{threadId}   - Lock key with TTL
{keyPrefix}:cache:{key}       - Cached key-value entry
{keyPrefix}:list:{key}        - List stored as JSON array
```

## Features

| Feature                  | Supported |
| ------------------------ | --------- |
| Persistence              | Yes       |
| Subscriptions            | Yes       |
| Key-value caching        | Yes       |
| Key prefix namespacing   | Yes       |
| Best-effort locking      | Yes       |
| Atomic distributed locks | No        |

## Limitations

Cloudflare KV has different characteristics than Redis. Be aware of these trade-offs:

### Eventual consistency

KV is eventually consistent. Reads may return stale data for up to 60 seconds after a write. This can affect `isSubscribed` checks and lock visibility across workers in different regions.

### Non-atomic locking

Lock operations (`acquireLock`, `releaseLock`, `extendLock`) and `setIfNotExists` use read-then-write patterns that are **not atomic**. Under high concurrency, two workers could both acquire the same lock. If you need reliable distributed locking, use [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/) instead.

### Minimum TTL

KV's minimum expiration TTL is 60 seconds. Shorter TTLs are enforced logically via stored expiration timestamps — the adapter will treat the lock as expired, but the KV key won't be physically deleted until at least 60 seconds have passed.

### Concurrent list appends

`appendToList` reads the full list, modifies it in memory, and writes it back. Concurrent appends from different workers may overwrite each other. For high-write list workloads, consider using Durable Objects or D1 instead.

## When to use this adapter

This adapter is a good fit when:

- You are running on Cloudflare Workers and want a simple state backend
- Your workload has low to moderate concurrency
- Eventual consistency is acceptable for your use case
- You don't need strict distributed locking guarantees

If you need strong consistency or atomic operations, consider Durable Objects or D1 as your state backend.

## License

MIT