update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -0,0 +1,89 @@
# Cloudflare Workers KV
Globally-distributed, eventually-consistent key-value store optimized for high read volume and low latency.
## Overview
KV provides:
- Eventual consistency (60s global propagation)
- Read-optimized performance
- 25 MiB value limit per key
- Auto-replication to Cloudflare edge
- Metadata support (1024 bytes)
**Use cases:** Config storage, user sessions, feature flags, caching, A/B testing
## When to Use KV
| Need | Recommendation |
|------|----------------|
| Strong consistency | → [Durable Objects](../durable-objects/) |
| SQL queries | → [D1](../d1/) |
| Object storage (files) | → [R2](../r2/) |
| High read, low write volume | → KV ✅ |
| Sub-10ms global reads | → KV ✅ |
**Quick comparison:**
| Feature | KV | D1 | Durable Objects |
|---------|----|----|-----------------|
| Consistency | Eventual | Strong | Strong |
| Read latency | <10ms | ~50ms | <1ms |
| Write limit | 1/s per key | Unlimited | Unlimited |
| Use case | Config, cache | Relational data | Coordination |
## Quick Start
```bash
wrangler kv namespace create MY_NAMESPACE
# Add binding to wrangler.jsonc
```
```typescript
// Write
await env.MY_KV.put("key", "value", { expirationTtl: 300 });
// Read
const value = await env.MY_KV.get("key");
const json = await env.MY_KV.get<Config>("config", "json");
```
## Core Operations
| Method | Purpose | Returns |
|--------|---------|---------|
| `get(key, type?)` | Single read | `string \| null` |
| `get(keys, type?)` | Bulk read (≤100) | `Map<string, T \| null>` |
| `put(key, value, options?)` | Write | `Promise<void>` |
| `delete(key)` | Delete | `Promise<void>` |
| `list(options?)` | List keys | `{ keys, list_complete, cursor? }` |
| `getWithMetadata(key)` | Get + metadata | `{ value, metadata }` |
## Consistency Model
- **Write visibility:** Immediate in same location, ≤60s globally
- **Read path:** Eventually consistent
- **Write rate:** 1 write/second per key (429 on exceed)
## Reading Order
| Task | Files to Read |
|------|---------------|
| Quick start | README → configuration.md |
| Implement feature | README → api.md → patterns.md |
| Debug issues | gotchas.md → api.md |
| Batch operations | api.md (bulk section) → patterns.md |
| Performance tuning | gotchas.md (performance) → patterns.md (caching) |
## In This Reference
- [configuration.md](./configuration.md) - wrangler.jsonc setup, namespace creation, TypeScript types
- [api.md](./api.md) - KV methods, bulk operations, cacheTtl, content types
- [patterns.md](./patterns.md) - Caching, sessions, rate limiting, A/B testing
- [gotchas.md](./gotchas.md) - Eventual consistency, concurrent writes, value limits
## See Also
- [workers](../workers/) - Worker runtime for KV access
- [d1](../d1/) - Use D1 for strong consistency needs
- [durable-objects](../durable-objects/) - Strongly consistent alternative

View File

@@ -0,0 +1,160 @@
# KV API Reference
## Read Operations
```typescript
// Single key (string)
const value = await env.MY_KV.get("user:123");
// JSON type (auto-parsed)
const config = await env.MY_KV.get<AppConfig>("config", "json");
// ArrayBuffer for binary
const buffer = await env.MY_KV.get("image", "arrayBuffer");
// Stream for large values
const stream = await env.MY_KV.get("large-file", "stream");
// With cache TTL (min 60s)
const value = await env.MY_KV.get("key", { type: "text", cacheTtl: 300 });
// Bulk get (max 100 keys, counts as 1 operation)
const keys = ["user:1", "user:2", "user:3", "missing:key"];
const results = await env.MY_KV.get(keys);
// Returns Map<string, string | null>
console.log(results.get("user:1")); // "John" (if exists)
console.log(results.get("missing:key")); // null
// Process results with null handling
for (const [key, value] of results) {
if (value !== null) {
// Handle found keys
console.log(`${key}: ${value}`);
}
}
// TypeScript with generics (type-safe JSON parsing)
interface UserProfile { name: string; email: string; }
const profile = await env.USERS.get<UserProfile>("user:123", "json");
// profile is typed as UserProfile | null
if (profile) {
console.log(profile.name); // Type-safe access
}
// Bulk get with type
const configs = await env.MY_KV.get<Config>(["config:app", "config:feature"], "json");
// Map<string, Config | null>
```
## Write Operations
```typescript
// Basic put
await env.MY_KV.put("key", "value");
await env.MY_KV.put("config", JSON.stringify({ theme: "dark" }));
// With expiration (UNIX timestamp)
await env.MY_KV.put("session", token, {
expiration: Math.floor(Date.now() / 1000) + 3600
});
// With TTL (seconds from now, min 60)
await env.MY_KV.put("cache", data, { expirationTtl: 300 });
// With metadata (max 1024 bytes)
await env.MY_KV.put("user:profile", userData, {
metadata: { version: 2, lastUpdated: Date.now() }
});
// Combined
await env.MY_KV.put("temp", value, {
expirationTtl: 3600,
metadata: { temporary: true }
});
```
## Get with Metadata
```typescript
// Single key
const result = await env.MY_KV.getWithMetadata("user:profile");
// { value: string | null, metadata: any | null }
if (result.value && result.metadata) {
const { version, lastUpdated } = result.metadata;
}
// Multiple keys (bulk)
const keys = ["key1", "key2", "key3"];
const results = await env.MY_KV.getWithMetadata(keys);
// Returns Map<string, { value, metadata, cacheStatus? }>
for (const [key, result] of results) {
if (result.value) {
console.log(`${key}: ${result.value}`);
console.log(`Metadata: ${JSON.stringify(result.metadata)}`);
// cacheStatus field indicates cache hit/miss (when available)
}
}
// With type
const result = await env.MY_KV.getWithMetadata<UserData>("user:123", "json");
// result: { value: UserData | null, metadata: any | null, cacheStatus?: string }
```
## Delete Operations
```typescript
await env.MY_KV.delete("key"); // Always succeeds (even if key missing)
```
## List Operations
```typescript
// List all
const keys = await env.MY_KV.list();
// { keys: [...], list_complete: boolean, cursor?: string }
// With prefix
const userKeys = await env.MY_KV.list({ prefix: "user:" });
// Pagination
let cursor: string | undefined;
let allKeys = [];
do {
const result = await env.MY_KV.list({ cursor, limit: 1000 });
allKeys.push(...result.keys);
cursor = result.cursor;
} while (!result.list_complete);
```
## Performance Considerations
### Type Selection
| Type | Use Case | Performance |
|------|----------|-------------|
| `stream` | Large values (>1MB) | Fastest - no buffering |
| `arrayBuffer` | Binary data | Fast - single allocation |
| `text` | String values | Medium |
| `json` | Objects (parse overhead) | Slowest - parsing cost |
### Parallel Reads
```typescript
// Efficient parallel reads with Promise.all()
const [user, settings, cache] = await Promise.all([
env.USERS.get("user:123", "json"),
env.SETTINGS.get("config:app", "json"),
env.CACHE.get("data:latest")
]);
```
## Error Handling
- **Missing keys:** Return `null` (not an error)
- **Rate limit (429):** Retry with exponential backoff (see gotchas.md)
- **Response too large (413):** Values >25MB fail with 413 error
See [gotchas.md](./gotchas.md) for detailed error patterns and solutions.

View File

@@ -0,0 +1,144 @@
# KV Configuration
## Create Namespace
```bash
wrangler kv namespace create MY_NAMESPACE
# Output: { binding = "MY_NAMESPACE", id = "abc123..." }
wrangler kv namespace create MY_NAMESPACE --preview # For local dev
```
## Workers Binding
**wrangler.jsonc:**
```jsonc
{
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "abc123xyz789"
},
// Optional: Different namespace for preview/development
{
"binding": "MY_KV",
"preview_id": "preview-abc123"
}
]
}
```
## TypeScript Types
**env.d.ts:**
```typescript
interface Env {
MY_KV: KVNamespace;
SESSIONS: KVNamespace;
CACHE: KVNamespace;
}
```
**worker.ts:**
```typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// env.MY_KV is now typed as KVNamespace
const value = await env.MY_KV.get("key");
return new Response(value || "Not found");
}
} satisfies ExportedHandler<Env>;
```
**Type-safe JSON operations:**
```typescript
interface UserProfile {
name: string;
email: string;
role: "admin" | "user";
}
const profile = await env.USERS.get<UserProfile>("user:123", "json");
// profile: UserProfile | null (type-safe!)
if (profile) {
console.log(profile.name); // TypeScript knows this is a string
}
```
## CLI Operations
```bash
# Put
wrangler kv key put --binding=MY_KV "key" "value"
wrangler kv key put --binding=MY_KV "key" --path=./file.json --ttl=3600
# Get
wrangler kv key get --binding=MY_KV "key"
# Delete
wrangler kv key delete --binding=MY_KV "key"
# List
wrangler kv key list --binding=MY_KV --prefix="user:"
# Bulk operations (max 10,000 keys per file)
wrangler kv bulk put data.json --binding=MY_KV
wrangler kv bulk get keys.json --binding=MY_KV
wrangler kv bulk delete keys.json --binding=MY_KV --force
```
## Local Development
```bash
wrangler dev # Local KV (isolated)
wrangler dev --remote # Remote KV (production)
# Or in wrangler.jsonc:
# "kv_namespaces": [{ "binding": "MY_KV", "id": "...", "remote": true }]
```
## REST API
### Single Operations
```typescript
import Cloudflare from 'cloudflare';
const client = new Cloudflare({
apiEmail: process.env.CLOUDFLARE_EMAIL,
apiKey: process.env.CLOUDFLARE_API_KEY
});
// Single key operations
await client.kv.namespaces.values.update(namespaceId, 'key', {
account_id: accountId,
value: 'value',
expiration_ttl: 3600
});
```
### Bulk Operations
```typescript
// Bulk update (up to 10,000 keys, max 100MB total)
await client.kv.namespaces.bulkUpdate(namespaceId, {
account_id: accountId,
body: [
{ key: "key1", value: "value1", expiration_ttl: 3600 },
{ key: "key2", value: "value2", metadata: { version: 1 } },
{ key: "key3", value: "value3" }
]
});
// Bulk get (up to 100 keys)
const results = await client.kv.namespaces.bulkGet(namespaceId, {
account_id: accountId,
keys: ["key1", "key2", "key3"]
});
// Bulk delete (up to 10,000 keys)
await client.kv.namespaces.bulkDelete(namespaceId, {
account_id: accountId,
keys: ["key1", "key2", "key3"]
});
```

View File

@@ -0,0 +1,131 @@
# KV Gotchas & Troubleshooting
## Common Errors
### "Stale Read After Write"
**Cause:** Eventual consistency means writes may not be immediately visible in other regions
**Solution:** Don't read immediately after write; return confirmation without reading or use the local value you just wrote. Writes visible immediately in same location, ≤60s globally
```typescript
// ❌ BAD: Read immediately after write
await env.KV.put("key", "value");
const value = await env.KV.get("key"); // May be null in other regions!
// ✅ GOOD: Use the value you just wrote
const newValue = "value";
await env.KV.put("key", newValue);
return new Response(newValue); // Don't re-read
```
### "429 Rate Limit on Concurrent Writes"
**Cause:** Multiple concurrent writes to same key exceeding 1 write/second limit
**Solution:** Use sequential writes, unique keys for concurrent operations, or implement retry with exponential backoff
```typescript
async function putWithRetry(
kv: KVNamespace,
key: string,
value: string,
maxAttempts = 5
): Promise<void> {
let delay = 1000;
for (let i = 0; i < maxAttempts; i++) {
try {
await kv.put(key, value);
return;
} catch (err) {
if (err instanceof Error && err.message.includes("429")) {
if (i === maxAttempts - 1) throw err;
await new Promise(r => setTimeout(r, delay));
delay *= 2; // Exponential backoff
} else {
throw err;
}
}
}
}
```
### "Inefficient Multiple Gets"
**Cause:** Making multiple individual get() calls instead of bulk operation
**Solution:** Use bulk get with array of keys: `env.USERS.get(["user:1", "user:2", "user:3"])` to reduce to 1 operation
### "Null Reference Error"
**Cause:** Attempting to use value without checking for null when key doesn't exist
**Solution:** Always handle null returns - KV returns `null` for missing keys, not undefined
```typescript
// ❌ BAD: Assumes value exists
const config = await env.KV.get("config", "json");
return config.theme; // TypeError if null!
// ✅ GOOD: Null checks
const config = await env.KV.get("config", "json");
return config?.theme ?? "default";
// ✅ GOOD: Early return
const config = await env.KV.get("config", "json");
if (!config) return new Response("Not found", { status: 404 });
return new Response(config.theme);
```
### "Negative Lookup Caching"
**Cause:** Keys that don't exist are cached as "not found" for up to 60s
**Solution:** Creating a key after checking won't be visible until cache expires
```typescript
// Check → create pattern has race condition
const exists = await env.KV.get("key"); // null, cached as "not found"
if (!exists) {
await env.KV.put("key", "value");
// Next get() may still return null for ~60s due to negative cache
}
// Alternative: Always assume key may not exist, use defaults
const value = await env.KV.get("key") ?? "default-value";
```
## Performance Tips
| Scenario | Recommendation | Why |
|----------|----------------|-----|
| Large values (>1MB) | Use `stream` type | Avoids buffering entire value in memory |
| Many small keys | Coalesce into one JSON object | Reduces operations, improves cache hit rate |
| High write volume | Spread across different keys | Avoid 1 write/second per-key limit |
| Cold reads | Increase `cacheTtl` parameter | Reduces latency for frequently-read data |
| Bulk operations | Use array form of get() | Single operation, better performance |
## Cost Examples
**Free tier:**
- 100K reads/day = 3M/month ✅
- 1K writes/day = 30K/month ✅
- 1GB storage ✅
**Example paid workload:**
- 10M reads/month = $5.00
- 100K writes/month = $0.50
- 1GB storage = $0.50
- **Total: ~$6/month**
## Limits
| Limit | Value | Notes |
|-------|-------|-------|
| Key size | 512 bytes | Maximum key length |
| Value size | 25 MiB | Maximum value; 413 error if exceeded |
| Metadata size | 1024 bytes | Maximum metadata per key |
| cacheTtl minimum | 60s | Minimum cache TTL |
| Write rate per key | 1 write/second | All plans; 429 error if exceeded |
| Propagation time | ≤60s | Global propagation time |
| Bulk get max | 100 keys | Maximum keys per bulk operation |
| Operations per Worker | 1,000 | Per request (bulk counts as 1) |
| Reads pricing | $0.50 per 10M | Per million reads |
| Writes pricing | $5.00 per 1M | Per million writes |
| Deletes pricing | $5.00 per 1M | Per million deletes |
| Storage pricing | $0.50 per GB-month | Per GB per month |

View File

@@ -0,0 +1,196 @@
# KV Patterns & Best Practices
## Multi-Tier Caching
```typescript
// Memory → KV → Origin (3-tier cache)
const memoryCache = new Map<string, { data: any; expires: number }>();
async function getCached(env: Env, key: string): Promise<any> {
const now = Date.now();
// L1: Memory cache (fastest)
const cached = memoryCache.get(key);
if (cached && cached.expires > now) {
return cached.data;
}
// L2: KV cache (fast)
const kvValue = await env.CACHE.get(key, "json");
if (kvValue) {
memoryCache.set(key, { data: kvValue, expires: now + 60000 }); // 1min in memory
return kvValue;
}
// L3: Origin (slow)
const origin = await fetch(`https://api.example.com/${key}`).then(r => r.json());
// Backfill caches
await env.CACHE.put(key, JSON.stringify(origin), { expirationTtl: 300 }); // 5min in KV
memoryCache.set(key, { data: origin, expires: now + 60000 });
return origin;
}
```
## API Response Caching
```typescript
async function getCachedData(env: Env, key: string, fetcher: () => Promise<any>): Promise<any> {
const cached = await env.MY_KV.get(key, "json");
if (cached) return cached;
const data = await fetcher();
await env.MY_KV.put(key, JSON.stringify(data), { expirationTtl: 300 });
return data;
}
const apiData = await getCachedData(
env,
"cache:users",
() => fetch("https://api.example.com/users").then(r => r.json())
);
```
## Session Management
```typescript
interface Session { userId: string; expiresAt: number; }
async function createSession(env: Env, userId: string): Promise<string> {
const sessionId = crypto.randomUUID();
const expiresAt = Date.now() + (24 * 60 * 60 * 1000);
await env.SESSIONS.put(
`session:${sessionId}`,
JSON.stringify({ userId, expiresAt }),
{ expirationTtl: 86400, metadata: { createdAt: Date.now() } }
);
return sessionId;
}
async function getSession(env: Env, sessionId: string): Promise<Session | null> {
const data = await env.SESSIONS.get<Session>(`session:${sessionId}`, "json");
if (!data || data.expiresAt < Date.now()) return null;
return data;
}
```
## Coalesce Cold Keys
```typescript
// ❌ BAD: Many individual keys
await env.KV.put("user:123:name", "John");
await env.KV.put("user:123:email", "john@example.com");
// ✅ GOOD: Single coalesced object
await env.USERS.put("user:123:profile", JSON.stringify({
name: "John",
email: "john@example.com",
role: "admin"
}));
// Benefits: Hot key cache, single read, reduced operations
// Trade-off: Harder to update individual fields
```
## Prefix-Based Namespacing
```typescript
// Logical partitioning within single namespace
const PREFIXES = {
users: "user:",
sessions: "session:",
cache: "cache:",
features: "feature:"
} as const;
// Write with prefix
async function setUser(env: Env, id: string, data: any) {
await env.KV.put(`${PREFIXES.users}${id}`, JSON.stringify(data));
}
// Read with prefix
async function getUser(env: Env, id: string) {
return await env.KV.get(`${PREFIXES.users}${id}`, "json");
}
// List by prefix
async function listUserIds(env: Env): Promise<string[]> {
const result = await env.KV.list({ prefix: PREFIXES.users });
return result.keys.map(k => k.name.replace(PREFIXES.users, ""));
}
// Example hierarchy
"user:123:profile"
"user:123:settings"
"cache:api:users"
"session:abc-def"
"feature:flags:beta"
```
## Metadata Versioning
```typescript
interface VersionedData {
version: number;
data: any;
}
async function migrateIfNeeded(env: Env, key: string) {
const result = await env.DATA.getWithMetadata(key, "json");
if (!result.value) return null;
const currentVersion = result.metadata?.version || 1;
const targetVersion = 2;
if (currentVersion < targetVersion) {
// Migrate data format
const migrated = migrate(result.value, currentVersion, targetVersion);
// Store with new version
await env.DATA.put(key, JSON.stringify(migrated), {
metadata: { version: targetVersion, migratedAt: Date.now() }
});
return migrated;
}
return result.value;
}
function migrate(data: any, from: number, to: number): any {
if (from === 1 && to === 2) {
// V1 → V2: Rename field
return { ...data, userName: data.name };
}
return data;
}
```
## Error Boundary Pattern
```typescript
// Resilient get with fallback
async function resilientGet<T>(
env: Env,
key: string,
fallback: T
): Promise<T> {
try {
const value = await env.KV.get<T>(key, "json");
return value ?? fallback;
} catch (err) {
console.error(`KV error for ${key}:`, err);
return fallback;
}
}
// Usage
const config = await resilientGet(env, "config:app", {
theme: "light",
maxItems: 10
});
```