Storage layer

Object/blob storage is a separate composition layer from relational backend and third-party integrations.

packages/core/src/storage/          apps/web/src/storage/
├── blob-ports.ts (IBlobStore)      ├── config.ts           ← BLOB_PROVIDER, R2, tiering knobs
└── (Phase 1: registry, tiering)    ├── wire-storage.ts     ← composition helper
                                    └── providers/
                                        supabase/           ← default today
                                        s3/                 ← Phase 1: R2 + MinIO
                                        tiered/             ← Phase 1: hot/cold facade

How it fits

Layer Responsibility Env prefix
backend/ Postgres repos, auth session, admin NEXT_PUBLIC_BACKEND_PROVIDER
storage/ Files: images, artifacts, vault assets BLOB_PROVIDER (server), NEXT_PUBLIC_BLOB_PROVIDER (browser), R2_*, BLOB_COLD_*
integrations/ Zotero, GitLab, Mattermost, … NEXT_PUBLIC_*_PROVIDER

Feature code uses IBlobStore via PaperImageStore — never Supabase Storage SDK directly.

Docs

Default today

BLOB_PROVIDER=supabase (or unset) → SupabaseBlobStore wired from wire-supabase-backend via wireStorage().

Phase 1 target

BLOB_PROVIDER=tieredTieredBlobStore (R2 hot, MinIO cold, blob_objects registry).