Skip to content

Key Value (KV)

Overview

The Raindrop framework provides a key-value storage interface for storing and retrieving data by unique keys. KV stores are ideal for caching, session storage, configuration management, and simple data persistence.

The KV interface supports multiple data types including text, JSON objects, binary data, and streams. It also provides metadata support, expiration controls, and listing capabilities for managing your stored data.

Prerequisites

  • Raindrop framework installed in your project
  • Familiarity with TypeScript and async/await patterns
  • Understanding of key-value storage concepts
  • Basic knowledge of data serialization formats (JSON, binary)

Configuration

Add a KV store to your Raindrop project by defining it in your application manifest:

application "demo-app" {
kv_store "demo-cache" {}
}

After running raindrop build generate, the store becomes available as env.DEMO_CACHE in your services and actors.

Access

Access KV stores through environment variables. The manifest name converts to uppercase with underscores, following the same pattern as other Raindrop resources. Once you access the KV store, you can immediately begin storing and retrieving data using the available methods.

// Manifest: kv_store "demo-cache"
// Access via: env.DEMO_CACHE
await env.DEMO_CACHE.put("user:123", "John Doe");

Core Interfaces

KvStore

KvStore<Key>

Main interface for key-value operations with type-safe key constraints:

interface KvStore<Key extends string = string> {
// Store data with optional expiration and metadata
put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KvStorePutOptions): Promise<void>;
// Retrieve values with type conversion
get(key: Key, type?: 'text'): Promise<string | null>;
get<ExpectedValue = unknown>(key: Key, type: 'json'): Promise<ExpectedValue | null>;
get(key: Key, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
get(key: Key, type: 'stream'): Promise<ReadableStream | null>;
// Retrieve values with metadata
getWithMetadata<Metadata = unknown>(key: Key, type?: 'text'): Promise<KvStoreGetWithMetadataResult<string, Metadata>>;
getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, type: 'json'): Promise<KvStoreGetWithMetadataResult<ExpectedValue, Metadata>>;
getWithMetadata<Metadata = unknown>(key: Key, type: 'arrayBuffer'): Promise<KvStoreGetWithMetadataResult<ArrayBuffer, Metadata>>;
getWithMetadata<Metadata = unknown>(key: Key, type: 'stream'): Promise<KvStoreGetWithMetadataResult<ReadableStream, Metadata>>;
// List stored keys with filtering
list<Metadata = unknown>(options?: KvStoreListOptions): Promise<KvStoreListResult<Metadata, Key>>;
// Remove keys from storage
delete(key: Key): Promise<void>;
}

KvStoreListResult

KvStoreListResult<Metadata, Key>

Result interface for list operations with pagination support:

type KvStoreListResult<Metadata, Key extends string = string> =
| {
list_complete: false; // More results available
keys: KvStoreListKey<Metadata, Key>[];
cursor: string; // Pagination cursor for next page
cacheStatus: string | null;
}
| {
list_complete: true; // All results returned
keys: KvStoreListKey<Metadata, Key>[];
cacheStatus: string | null;
};

KvStoreGetWithMetadataResult

KvStoreGetWithMetadataResult<Value, Metadata>

Result interface for metadata-aware get operations:

interface KvStoreGetWithMetadataResult<Value, Metadata> {
value: Value | null; // The stored value or null if not found
metadata: Metadata | null; // Custom metadata object or null
cacheStatus: string | null; // Cache status information
}

KvStorePutOptions

Configuration options for put operations:

interface KvStorePutOptions {
expiration?: number; // Unix timestamp for absolute expiration
expirationTtl?: number; // TTL in seconds (minimum 60 seconds)
metadata?: any | null; // Custom metadata object
}

KvStoreGetOptions

KvStoreGetOptions<Type>

Configuration options for get operations:

interface KvStoreGetOptions<Type> {
type: Type; // Return type: 'text' | 'json' | 'arrayBuffer' | 'stream'
cacheTtl?: number; // Cache TTL for the operation
}

KvStoreListOptions

Configuration options for list operations:

interface KvStoreListOptions {
limit?: number; // Maximum number of keys to return
prefix?: string | null; // Filter keys by prefix
cursor?: string | null; // Pagination cursor from previous call
}

Storage Methods

put(key, value, options?)

Stores a value with the given key.

// Store text
await env.DEMO_CACHE.put("user:123", "John Doe");
// Store with expiration (TTL in seconds)
await env.DEMO_CACHE.put("session:abc", sessionData, {
expirationTtl: 3600 // 1 hour
});
// Store with metadata
await env.DEMO_CACHE.put("file:456", fileData, {
metadata: { contentType: "image/png", size: 1024 }
});

Options:

  • expirationTtl: Expire after N seconds (minimum 60 seconds)
  • expiration: Expire at Unix timestamp (must be in the future)
  • metadata: Custom metadata object

get(key, type?)

Retrieves a value by key with optional type conversion.

// Get as text (default)
const value = await env.DEMO_CACHE.get("user:123");
// Get as JSON
const data = await env.DEMO_CACHE.get("config:settings", "json");
// Get as binary data
const buffer = await env.DEMO_CACHE.get("file:456", "arrayBuffer");
// Get as stream
const stream = await env.DEMO_CACHE.get("large:file", "stream");

Types:

  • "text" (default): Returns string
  • "json": Parses JSON and returns object
  • "arrayBuffer": Returns ArrayBuffer
  • "stream": Returns ReadableStream

Returns null if key doesn’t exist or has expired.

Metadata Methods

getWithMetadata(key, type?)

Retrieves value along with metadata and cache information.

const result = await env.DEMO_CACHE.getWithMetadata("file:456", "json");
console.log(result.value); // The stored value
console.log(result.metadata); // Custom metadata
console.log(result.cacheStatus); // Cache status string or null

Response:

  • value: The stored value or null if not found
  • metadata: Custom metadata object or null
  • cacheStatus: Cache status string or null

List Methods

list(options?)

Lists stored keys with optional filtering and pagination.

// List all keys
const allKeys = await env.DEMO_CACHE.list();
// List with prefix filter
const userKeys = await env.DEMO_CACHE.list({ prefix: "user:" });
// List with limit and pagination
const batch = await env.DEMO_CACHE.list({
prefix: "session:",
limit: 100,
cursor: "..." // from previous call
});

Options:

  • prefix: Filter keys by prefix
  • limit: Maximum number of keys to return
  • cursor: Pagination cursor from previous call

Response:

  • keys: Array of key objects with name, expiration, metadata
  • list_complete: Boolean indicating if more results exist
  • cursor: Use for next page (if list_complete is false)

delete(key)

Removes a key from storage.

await env.DEMO_CACHE.delete("user:123");

The operation is idempotent - deleting a non-existent key doesn’t throw an error.

Data Types

Text Storage

Store and retrieve simple string values like configuration settings, user preferences, or short text data.

await env.DEMO_CACHE.put("config:theme", "dark");
const theme = await env.DEMO_CACHE.get("config:theme"); // "dark"

JSON Storage

Store complex objects and data structures by serializing them as JSON strings.

const user = { id: 123, name: "John", active: true };
await env.DEMO_CACHE.put("user:123", JSON.stringify(user));
const userData = await env.DEMO_CACHE.get("user:123", "json");

Binary Storage

Store binary data like images, files, or any raw bytes using ArrayBuffer.

const imageData = new ArrayBuffer(1024);
await env.DEMO_CACHE.put("image:456", imageData);
const retrievedData = await env.DEMO_CACHE.get("image:456", "arrayBuffer");

Stream Storage

Store large data as streams for memory-efficient handling of big files or continuous data.

const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("chunk1"));
controller.enqueue(new TextEncoder().encode("chunk2"));
controller.close();
}
});
await env.DEMO_CACHE.put("file:789", stream);
const retrievedStream = await env.DEMO_CACHE.get("file:789", "stream");

Examples

Basic Usage

Simple session management using KV storage with automatic expiration.

export default class extends Service<Env> {
async fetch(request: Request): Promise<Response> {
// Store user session
const sessionId = "abc123";
await this.env.DEMO_CACHE.put(`session:${sessionId}`, JSON.stringify({
userId: 123,
loginTime: new Date().toISOString()
}), {
expirationTtl: 24 * 60 * 60 // 24 hours
});
// Retrieve session
const sessionData = await this.env.DEMO_CACHE.get(`session:${sessionId}`, "json");
if (!sessionData) {
return new Response("Session not found", { status: 401 });
}
return new Response(`Welcome user ${sessionData.userId}`);
}
}

Cache Pattern

Implement read-through caching to reduce database load and improve response times.

async getUser(userId: string) {
// Check cache first
let user = await this.env.DEMO_CACHE.get(`user:${userId}`, "json");
if (!user) {
// Cache miss - fetch from database
user = await this.database.getUser(userId);
// Cache for 5 minutes
await this.env.DEMO_CACHE.put(`user:${userId}`, JSON.stringify(user), {
expirationTtl: 300
});
}
return user;
}

Cleanup Pattern

Manually clean up expired entries by listing and filtering based on expiration times.

async cleanupExpiredSessions() {
const sessions = await this.env.DEMO_CACHE.list({ prefix: "session:" });
const now = Date.now() / 1000;
for (const key of sessions.keys) {
if (key.expiration && key.expiration < now) {
await this.env.DEMO_CACHE.delete(key.name);
}
}
}