FUNCTIONS / @SPFN/STORAGE — PROVIDER-AGNOSTIC OBJECT STORAGE

Files,
handled.

One interface over Google Cloud Storage, S3-compatible services, and the local filesystem. @spfn/storage ships presigned upload, direct upload and download, public URLs, finalization, and deletion — and owns no database. It speaks in object keys, never arbitrary URLs.

Read the reference Get started $ pnpm add @spfn/storage
01
The premise

Six backends.
One interface.

Every provider does storage its own way — GCS signs size ranges, S3 can't; S3 tags temp objects, GCS moves them; local disk supports no presigning at all. @spfn/storage puts one service in front of GCS, S3, R2, MinIO, Wasabi, and the local filesystem, and documents exactly what each backend enforces instead of papering over the differences. Your app holds the keys in its own schema; the package handles the objects and nothing else.

your app
getStorageService()
object key
gcs · s3-compatible · local
FIG 01 — KEYS IN, OBJECTS OUT · NO DATABASE OWNED · PUBLIC/* ROUTES TO THE PUBLIC BUCKET
02
Core technique

The limit lives in
the signature.

A server-side check of a client-declared file size binds nothing — the client can declare one byte and PUT gigabytes to the presigned URL. So @spfn/storage signs the limit into the URL itself: pass maxBytes or contentLength when presigning, and the returned requiredHeaders must be sent verbatim on the PUT, or the signature is invalid and the provider rejects the upload. On GCS the signed range turns any out-of-range upload into an HTTP 400. And where a provider can't enforce, the package won't pretend: S3-compatible presigned PUTs cannot sign a size range, so maxBytes is ignored there — only an exact, signed Content-Length is enforced.

getUploadUrl({ maxBytes })
uploadUrl + requiredHeaders
client PUT, headers verbatim
storage enforces the size
FIG 02 — GCS SIGNS THE RANGE · S3 SIGNS EXACT CONTENT-LENGTH · UNENFORCEABLE = IGNORED, NOT FAKED
03
What's inside

The parts you'd otherwise
build again.

Store it right.