tx-agent-kit
Storage

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.

ComponentLocationPurpose
Storage servicepackages/infra/storageS3 client, presigned URLs, CRUD operations
API routesapps/api/src/routes/storage.tsHTTP endpoints for upload/download/delete
Worker activitiesapps/worker/src/activities.tsBackground 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

  1. Client requests a presigned upload URL from the API (POST /v1/storage/upload-url)
  2. API authenticates the request, generates a presigned PUT URL via R2
  3. Client uploads the file directly to R2 using the presigned URL
  4. 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.

EnvironmentBucketWhen used
Developmentoctospark-devLocal dev, CI
Stagingoctospark-stagingPre-production testing
Productionoctospark-prodLive 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 StorageLive layer 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-services vault and injected via op:// references.
PageDescription
ConfigurationEnvironment variables and 1Password setup
API EndpointsHTTP routes for storage operations
Worker ActivitiesBackground storage operations
InfrastructureDocker stack and local development
Secrets Management1Password CLI integration

On this page