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_CACHEawait 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 textawait 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 metadataawait 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 JSONconst data = await env.DEMO_CACHE.get("config:settings", "json");
// Get as binary dataconst buffer = await env.DEMO_CACHE.get("file:456", "arrayBuffer");
// Get as streamconst 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 valueconsole.log(result.metadata); // Custom metadataconsole.log(result.cacheStatus); // Cache status string or null
Response:
value
: The stored value ornull
if not foundmetadata
: Custom metadata object ornull
cacheStatus
: Cache status string ornull
List Methods
list(options?)
Lists stored keys with optional filtering and pagination.
// List all keysconst allKeys = await env.DEMO_CACHE.list();
// List with prefix filterconst userKeys = await env.DEMO_CACHE.list({ prefix: "user:" });
// List with limit and paginationconst batch = await env.DEMO_CACHE.list({ prefix: "session:", limit: 100, cursor: "..." // from previous call});
Options:
prefix
: Filter keys by prefixlimit
: Maximum number of keys to returncursor
: Pagination cursor from previous call
Response:
keys
: Array of key objects withname
,expiration
,metadata
list_complete
: Boolean indicating if more results existcursor
: Use for next page (iflist_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); } }}