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.
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.
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.
GCS, S3, R2, MinIO, Wasabi, and local disk behind one service.
maxBytes / contentLength signed into the presigned URL, enforced by the provider.
temp: true marks unconfirmed uploads so orphans expire by lifecycle rule.
finalizeObject confirms an upload — idempotent, rejects when nothing was uploaded.
delete(key) is idempotent — a missing key succeeds, real errors reject.
deleteMany returns per-key results; S3 batches up to 1,000 keys.
Origin deletion purges neither caches nor old versions — the caveats, written down.
A retryable outbox ordering for deletes that span database and bucket.