Cloudflare
function create_cloudflare_backend(config: CloudflareBackendConfig): BackendThe Cloudflare backend provides production-ready, globally distributed storage using Cloudflare’s infrastructure. It uses D1 (SQLite at the edge) for metadata queries and R2 (S3-compatible object storage) for binary data.
Why Use Cloudflare Backend?
- Global distribution: Data replicated across Cloudflare’s network
- Edge performance: Low latency from any location
- Serverless: No servers to manage, scales automatically
- Cost effective: Pay only for what you use
- Durable: Enterprise-grade storage reliability
Basic Usage
import { create_corpus, create_cloudflare_backend, define_store, json_codec} from '@f0rbit/corpus/cloudflare'import { z } from 'zod'
const SessionSchema = z.object({ userId: z.string(), data: z.record(z.unknown()), expiresAt: z.string(),})
export default { async fetch(request: Request, env: Env): Promise<Response> { const backend = create_cloudflare_backend({ d1: env.CORPUS_DB, r2: env.CORPUS_BUCKET, })
const corpus = create_corpus() .with_backend(backend) .with_store(define_store('sessions', json_codec(SessionSchema))) .build()
// Handle request using corpus... return new Response('OK') }}Configuration
type CloudflareBackendConfig = { d1: D1Database r2: R2Bucket on_event?: EventHandler}| Option | Type | Description |
|---|---|---|
d1 | D1Database | D1 database binding from env |
r2 | R2Bucket | R2 bucket binding from env |
on_event | EventHandler | Optional callback for storage events |
Setup Guide
1. Create D1 Database
# Create the databasewrangler d1 create corpus-db
# Note the database_id from the output2. Create R2 Bucket
wrangler r2 bucket create corpus-bucket3. Set Up Database Migrations
Corpus uses Drizzle ORM for schema management. Install Drizzle Kit and generate migrations from the exported schemas:
bun add -D drizzle-kitCreate drizzle.config.ts:
import { defineConfig } from 'drizzle-kit'
export default defineConfig({ dialect: 'sqlite', driver: 'd1-http', schema: [ './node_modules/@f0rbit/corpus/schema.js', './node_modules/@f0rbit/corpus/observations/schema.js', ], out: './migrations',})Generate and apply migrations:
# Generate migration files from corpus schemasbunx drizzle-kit generate
# Apply migrations to your D1 databasewrangler d1 migrations apply corpus-dbThis creates the corpus_snapshots and corpus_observations tables with all required indexes. See the Schema API for full schema details.
4. Configure wrangler.toml
name = "my-worker"main = "src/index.ts"compatibility_date = "2024-01-01"
[[d1_databases]]binding = "CORPUS_DB"database_name = "corpus-db"database_id = "your-database-id-here"
[[r2_buckets]]binding = "CORPUS_BUCKET"bucket_name = "corpus-bucket"5. TypeScript Types
interface Env { CORPUS_DB: D1Database CORPUS_BUCKET: R2Bucket}Complete Worker Example
import { create_corpus, create_cloudflare_backend, define_store, json_codec} from '@f0rbit/corpus/cloudflare'import { z } from 'zod'
const NoteSchema = z.object({ id: z.string(), title: z.string(), content: z.string(), createdAt: z.string(),})
type Note = z.infer<typeof NoteSchema>
interface Env { CORPUS_DB: D1Database CORPUS_BUCKET: R2Bucket}
export default { async fetch(request: Request, env: Env): Promise<Response> { const corpus = create_corpus() .with_backend(create_cloudflare_backend({ d1: env.CORPUS_DB, r2: env.CORPUS_BUCKET, })) .with_store(define_store('notes', json_codec(NoteSchema))) .build()
const url = new URL(request.url)
// GET /notes - List all notes if (url.pathname === '/notes' && request.method === 'GET') { const notes: Note[] = [] for await (const meta of corpus.stores.notes.list({ limit: 100 })) { const result = await corpus.stores.notes.get(meta.version) if (result.ok) notes.push(result.value.data) } return Response.json(notes) }
// POST /notes - Create a note if (url.pathname === '/notes' && request.method === 'POST') { const body = await request.json() as Omit<Note, 'id' | 'createdAt'> const note: Note = { ...body, id: crypto.randomUUID(), createdAt: new Date().toISOString(), }
const result = await corpus.stores.notes.put(note) if (result.ok) { return Response.json({ id: note.id, version: result.value.version }, { status: 201 }) } return Response.json({ error: result.error.kind }, { status: 500 }) }
return new Response('Not Found', { status: 404 }) }}Import Path
Error Handling
Handle Cloudflare-specific errors gracefully:
const result = await corpus.stores.data.put(item)
if (!result.ok) { switch (result.error.kind) { case 'storage_error': // D1 or R2 operation failed console.error(`${result.error.operation} failed:`, result.error.cause) return new Response('Storage error', { status: 503 })
case 'encode_error': return new Response('Invalid data format', { status: 400 })
default: return new Response('Internal error', { status: 500 }) }}When to Use
| Scenario | Recommended |
|---|---|
| Cloudflare Workers | ✅ Yes |
| Production workloads | ✅ Yes |
| Global applications | ✅ Yes |
| High availability needs | ✅ Yes |
| Local development | ⚠️ Use Memory for tests |
| Non-Cloudflare hosting | ❌ No |
Performance Tips
- Batch reads: Use
list()to get metadata, then fetch only what you need - Deduplication: Corpus automatically deduplicates content, reducing R2 storage
- Caching: Use Layered backend with Memory for hot data
- Indexes: D1 indexes are created by the migration for common queries
See Also
- Cloudflare Deployment Guide - Complete setup walkthrough
- Memory - For local testing
- Layered - Add caching layer