Introduction
@visulima/storage
Unified file storage for Node.js, edge runtimes, and modern web frameworks. One library, two surfaces:
Files— one-liner API for ad-hoc storage operations. Web-standard body types (Blob,Uint8Array,ReadableStream), a compact set of provider-agnostic methods, bulk-array overloads, lifecycle hooks (onAction/onError/onRetry), ranged downloads, upload progress, async-iterable listings, and a cross-providertransfer()for migrations. Use this when you just need to put files somewhere.BaseStorageadapters (S3Storage,DiskStorage,BunnyStorage, …) — the full upload-server framework. TUS / multipart / REST handlers, lifecycle hooks, validators, image / video / audio transformers, OpenAPI export. Use this when you're hosting an upload service.
Both surfaces wrap the same adapters — swap providers without touching call sites.
Install
npm install @visulima/storage
# or yarn add / pnpm addQuick start with Files
import { Files } from "@visulima/storage";
import { S3Storage } from "@visulima/storage/provider/aws";
const files = new Files({
adapter: new S3Storage({ bucket: "uploads", region: "us-east-1" }),
defaults: { timeout: 30_000, retries: { maxRetries: 5 } },
hooks: {
onAction: ({ type, key, durationMs }) => log.info(type, key, durationMs),
onRetry: ({ type, key, attempt }) => log.warn(type, key, "retry", attempt),
},
});
await files.upload("avatars/abc.png", buffer, { contentType: "image/png" });
const url = await files.url("avatars/abc.png", { expiresIn: 900 });
const { body } = await files.download("avatars/abc.png", { range: { start: 0, end: 1023 } });
// Bulk: upload / download / delete / move accept an array and return a structured result.
const { uploaded, errors } = await files.upload([
{ key: "a.txt", body: "A" },
{ key: "b.txt", body: "B" },
]);
// Walk every object as an async iterable — pages internally.
for await (const file of files.listAll({ prefix: "avatars/" })) {
log.info(file.key, file.size);
}For ephemeral environments and tests, an in-memory adapter ships out of the box:
import MemoryStorage from "@visulima/storage/provider/memory";
const files = new Files({ adapter: new MemoryStorage({ initial: { "k.txt": "x" } }) });Swap the adapter to change provider — everything else stays the same:
import { Files, DiskStorage } from "@visulima/storage";
import { BunnyStorage } from "@visulima/storage/provider/bunny";
new Files({ adapter: new DiskStorage({ directory: "./uploads" }) });
new Files({ adapter: new BunnyStorage({ zone, accessKey, region: "de" }) });The Files facade accepts any web-standard body (string, Buffer, Uint8Array, ArrayBuffer, Blob, ReadableStream, or a Node Readable) and returns provider-agnostic FileObject results.
→ See the Files facade reference for the full method surface.
Building an upload server?
If you're handling client uploads from a browser — large files, resumable transfers, form posts — use the adapter directly with one of the HTTP handlers. The Files facade is a one-shot put; upload servers need streaming, locking, and partial-write protocols.
Node.js / Express
import { DiskStorage } from "@visulima/storage";
import { Multipart, Rest } from "@visulima/storage/handler/http/node";
const storage = new DiskStorage({ directory: "./uploads" });
const multipart = new Multipart({ storage });
app.use("/upload", multipart.handle, (req, res) => {
res.json(req.body); // handler writes the stored file metadata to req.body
});
const rest = new Rest({ storage });
app.use("/files", rest.handle);Hono / Cloudflare Workers / Bun / Deno
import { Hono } from "hono";
import { DiskStorage } from "@visulima/storage";
import { createStorageHandler } from "@visulima/storage/handler/http/hono";
const app = new Hono();
const storage = new DiskStorage({ directory: "./uploads" });
createStorageHandler(app, { path: "/files", storage, type: "multipart" });
createStorageHandler(app, { path: "/files-tus", storage, type: "tus" });Three handlers cover the common upload patterns:
- Multipart —
multipart/form-datafrom HTML forms. - REST — direct binary
POST/PUT. Optional client-side chunking for large files. - TUS — resumable uploads (tus.io) for unreliable networks and very large files.
→ See TUS handler, Chunked uploads, Authenticated uploads.
Choosing your surface
| You want to… | Use |
|---|---|
Save a Blob / Buffer to S3 and get a signed URL | Files |
| Bulk-upload / bulk-delete a list of keys | Files (array overload on every method) |
| Migrate every object from one provider to another | transfer(source, dest) on Files |
| Walk every object in a bucket as an async iterable | files.listAll({ prefix }) |
| Observe operation counts, durations, retries from the facade | Files with hooks: { onAction, onRetry } |
| Drop in a file picker / "import from cloud" flow | Files with a consumer provider adapter |
| Expose ad-hoc storage to an AI agent or LLM tool | Files + @visulima/storage/ai/* subpaths |
| Build a TUS / multipart / chunked upload endpoint | adapter + handler |
| Transform images / video / audio on the fly | adapter + transformer |
Hook adapter-level onCreate / onComplete / onDelete | adapter |
| Export OpenAPI for an upload endpoint | adapter + OpenAPI export |
Providers
Object storage (service credentials, presigned URLs, S3-style):
- AWS S3 (
S3Storage,AWSLightStorage) - Azure Blob (
AzureStorage) - Google Cloud Storage (
GcsStorage) - Vercel Blob (
VercelBlobStorage) - Netlify Blobs (
NetlifyBlobStorage) - Local filesystem (
DiskStorage,DiskStorageWithChecksum) - In-memory (
MemoryStorage) — for tests and ephemeral environments
S3-compatible (branded client configs under provider/aws/s3/clients): Cloudflare R2, DigitalOcean Spaces, MinIO, Hetzner, Storj, Backblaze B2, Tigris, Wasabi, Akamai.
Consumer providers (user OAuth or service-credential, all peer-dep gated):
- Dropbox, Google Drive, Microsoft OneDrive, Box
- Supabase Storage, UploadThing, Bunny Storage
→ See Storage services for the full capability matrix per provider.
What you get beyond Files
The Files facade is intentionally small. Features below live on the adapter and the framework around it:
- Upload protocols — multipart-form, raw REST, chunked REST, TUS resumable.
- HTTP handlers — Node, Fetch, Hono, Next.js, SolidStart, Nuxt, Bun, Deno, Cloudflare, Edge.
- Image / video / audio transformers — Sharp + Mediabunny, URL-query or programmatic, with caching.
- Retry + backoff — exponential retry baked into every adapter, plus custom
shouldRetry. - Observability — built-in metrics and OpenTelemetry spans.
- Lifecycle hooks —
onCreate,onUpdate,onComplete,onDelete,onError. - Batch operations —
deleteBatch,copyBatch,moveBatchwith per-item error capture. - Validators, locking, expiration, checksums — content / size / MIME limits, lock tokens, automatic purge, MD5/SHA1/SHA256.
- OpenAPI export — generate a schema for your upload endpoint.
@visulima/storage-client— React / Vue / Solid / Svelte hooks with progress, drag-drop, paste, and abort.- AI-tool wrappers — Vercel AI SDK, OpenAI Responses / Agents, Claude Agent SDK, TanStack AI.
Next steps
- Files facade reference — full surface (single + bulk, hooks, ranges, progress,
transfer). - Storage services — per-provider configuration and capability matrix.
- Custom storage — write your own adapter against
BaseStorage. - Retry mechanism — backoff, custom
shouldRetry,onRetryhook. - Error handling —
UploadError,ERRORSenum, andwrapStorageError. - Observability — metrics, OpenTelemetry, structured logs.