Storage Overview
Cloudflare R2 object storage for images, media, and file content across all environments
tx-agent-kit uses Cloudflare R2 for all object storage across development, staging, and production. R2 provides an S3-compatible API with zero egress fees, making it cost-effective for image and content delivery.
Architecture
Storage is implemented as a first-class infra package (@tx-agent-kit/storage) that both the API and Worker consume through Effect's dependency injection.
| Component | Location | Purpose |
|---|---|---|
| Storage service | packages/infra/storage | S3 client, presigned URLs, CRUD operations |
| API routes | apps/api/src/routes/storage.ts | HTTP endpoints for upload/download/delete |
| Worker activities | apps/worker/src/activities.ts | Background storage operations (cleanup, listing) |
How it works
The storage package wraps @aws-sdk/client-s3 with an Effect service layer. Applications never interact with S3 directly; they yield the Storage service and call typed methods that return Effect.Effect values.
import { Storage } from '@tx-agent-kit/storage'
import { Effect } from 'effect'
const program = Effect.gen(function* () {
const storage = yield* Storage
// Generate a presigned upload URL for the client
const uploadUrl = yield* storage.generateUploadUrl(
'org-123/avatar.png',
'image/png',
300 // expires in 5 minutes
)
// Client uploads directly to R2 via the presigned URL
return { uploadUrl }
})Upload flow
- Client requests a presigned upload URL from the API (
POST /v1/storage/upload-url) - API authenticates the request, generates a presigned PUT URL via R2
- Client uploads the file directly to R2 using the presigned URL
- Client confirms the upload and stores the object key in application data
This pattern keeps large file payloads off the API server. The API only brokers the signed URL; the actual bytes flow directly between the client and R2.
Environment buckets
Each environment uses a dedicated R2 bucket. The same API token (scoped to all buckets on the account) works across all environments.
| Environment | Bucket | When used |
|---|---|---|
| Development | octospark-dev | Local dev, CI |
| Staging | octospark-staging | Pre-production testing |
| Production | octospark-prod | Live traffic |
Key design decisions
- No local emulator. All environments hit real R2. This eliminates environment parity issues and leverages R2's generous free tier (10 GB storage, 10M reads/month).
- Presigned URLs only. Files never pass through the API server. Clients upload and download directly from R2.
- Lazy initialization. The
StorageLivelayer defers S3 client creation until first use, so importing the package in tests does not require credentials. - 1Password for credentials. R2 access keys are stored in the
octospark-servicesvault and injected viaop://references.
Related pages
| Page | Description |
|---|---|
| Configuration | Environment variables and 1Password setup |
| API Endpoints | HTTP routes for storage operations |
| Worker Activities | Background storage operations |
| Infrastructure | Docker stack and local development |
| Secrets Management | 1Password CLI integration |