Object Storage
SDF Server uses S3-compatible object storage to persist .sdf files. The storage adapter is implemented in packages/sdf-server-core/src/storage/s3.ts using native fetch() and AWS Signature Version 4. No AWS SDK is used.
Object key format
Every .sdf file is stored under a deterministic key:
{tenant_id}/{year}/{month}/{document_id}.sdfExample:
c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6/2026/03/a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6.sdfThis layout naturally partitions objects by tenant and provides time-based locality for lifecycle policies.
Why no AWS SDK
The S3 adapter uses native fetch() with a hand-rolled AWS Signature V4 implementation. This keeps the package dependency-free with respect to AWS — no @aws-sdk/client-s3 is added to the lockfile. The adapter works identically against AWS S3, MinIO, Cloudflare R2, and any other S3-compatible storage backend.
AWS S3 setup
1. Create a bucket
aws s3api create-bucket \ --bucket sdf-documents-production \ --region eu-central-1 \ --create-bucket-configuration LocationConstraint=eu-central-12. Block public access
aws s3api put-public-access-block \ --bucket sdf-documents-production \ --public-access-block-configuration \ BlockPublicAcls=true,IgnorePublicAcls=true,\ BlockPublicPolicy=true,RestrictPublicBuckets=true3. Configure lifecycle (optional but recommended)
{ "Rules": [ { "ID": "archive-old-documents", "Status": "Enabled", "Filter": { "Prefix": "" }, "Transitions": [ { "Days": 90, "StorageClass": "GLACIER" } ] } ]}aws s3api put-bucket-lifecycle-configuration \ --bucket sdf-documents-production \ --lifecycle-configuration file://lifecycle.json4. Set environment variables
S3_ENDPOINT=https://s3.eu-central-1.amazonaws.comS3_BUCKET=sdf-documents-productionS3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLES3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYMinIO (local development)
MinIO provides an S3-compatible API and is the recommended backend for local development and testing.
The included docker-compose.yml starts MinIO automatically:
minio: image: minio/minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin ports: - "9000:9000" # S3 API - "9001:9001" # Admin UIAfter starting MinIO, create the bucket:
# Using the MinIO CLI (mc)mc alias set local http://localhost:9000 minioadmin minioadminmc mb local/sdf-documentsOr use the web UI at http://localhost:9001.
Environment variables for MinIO:
S3_ENDPOINT=http://localhost:9000S3_BUCKET=sdf-documentsS3_ACCESS_KEY=minioadminS3_SECRET_KEY=minioadminPre-signed URLs
Download endpoints return the .sdf binary directly through the server by default. For large-scale deployments where you want the client to download directly from S3, the storage adapter supports generating pre-signed GET URLs with a 1-hour TTL:
const url = await s3.presignGet(storageKey, { expiresIn: 3600 });// Returns a time-limited signed URL for direct S3 accessPre-signed URL generation is handled inside the storage adapter without any additional dependencies.
Supported operations
| Operation | Description |
|---|---|
put(key, body) | Upload a .sdf file |
get(key) | Download a .sdf file |
delete(key) | Delete a .sdf file |
exists(key) | Check if an object exists (HEAD request) |
presignGet(key, opts) | Generate a pre-signed GET URL |
All operations are async and throw a typed StorageError on failure.
Security
- All communication with S3/MinIO must use HTTPS in production. The
S3_ENDPOINTmust begin withhttps://in production environments. - The server process only requires
s3:PutObject,s3:GetObject,s3:DeleteObject, ands3:HeadObjecton the configured bucket. No other S3 permissions are needed. - Objects are never made publicly accessible. Access is always proxied through the server, enforcing tenant-level authorization before any object is served.
- Pre-signed URLs are scoped to a single object and expire after 1 hour.