Storage Architecture
Storage has two runtime paths: filesystem access and upload persistence.
Filesystem access flow
fs()callsFileSystemFactory::get()- config is imported if needed
- requested adapter (or
fs.default) is resolved - adapter is wrapped in
FileSystem - wrapper is cached per adapter name in the process
Practical effect: repeated fs('local') calls reuse the same wrapper instance.
Wrapper model
FileSystem is a delegating wrapper around one adapter.
Unsupported adapter methods are rejected with exceptions instead of being ignored.
Upload flow
UploadedFile runs this sequence:
- parse upload metadata
- detect extension + MIME from the local temp file
- load MIME policy (defaults + optional config)
- validate the temp file and destination strategy
- write locally or forward the temp file content to a remote adapter
- optionally run post-save image modification against the saved path
Practical effect: upload inspection and staging still depend on the local adapter, even when the final destination is remote.
Cloud composition
Cloud backends are composed from:
- adapter implementation
- cloud app/token service wiring from config
This keeps app-level usage consistent (fs() + wrapper API) while backend auth/token lifecycle stays in services/config.