Disk Storage โ
Universal file storage abstraction for Node.js and Bun. Write once, store anywhere โ filesystem, S3, Azure Blob, or any cloud provider.
Standalone package โ
@minimajs/diskhas no dependency on the rest of the minimajs framework. Use it in any Node.js or Bun project, with Express, Fastify, Hono, or no HTTP framework at all.
Features โ
- ๐ Universal API - Same interface for all storage providers
- ๐ฆ Multiple Drivers - Filesystem, S3, Azure, Memory, and more
- ๐ Protocol Routing - Route to different drivers based on URL prefixes
- ๐ Streaming - Efficient stream-based operations
- ๐ Type Safe - Full TypeScript support
- ๐งฉ Plugins -
storeAs,partition,atomicWrite,checksum,uploadProgress,downloadProgress,compression,encryptionand more - ๐ Web Standards - Works with
File,Blob, andReadableStreamnatively - ๐ File Integrity -
put(file)preserves the original filename by default - ๐งช Testing - Memory driver for fast unit tests
Installation โ
npm install @minimajs/disk
# or
bun add @minimajs/diskFor cloud providers:
npm install @minimajs/aws-s3
npm install @minimajs/azure-blobQuick Start โ
Filesystem Storage โ
import { createDisk } from "@minimajs/disk";
import { createFsDriver } from "@minimajs/disk/adapters";
const disk = createDisk({
driver: createFsDriver({
root: "/var/uploads",
publicUrl: "https://cdn.example.com",
}),
});
// Store a file
const file = await disk.put("avatar.jpg", imageData);
console.log(file.href); // file:///var/uploads/avatar.jpg
// Retrieve a file
const retrieved = await disk.get("avatar.jpg");
if (retrieved) {
const buffer = await retrieved.arrayBuffer();
const text = await retrieved.text();
const stream = retrieved.stream();
}
// Check existence
const exists = await disk.exists("avatar.jpg");
// Get public URL
const url = await disk.url("avatar.jpg");
console.log(url); // https://cdn.example.com/avatar.jpg
// Delete
await disk.delete("avatar.jpg");AWS S3 Storage โ
import { createDisk } from "@minimajs/disk";
import { createS3Driver } from "@minimajs/aws-s3";
const disk = createDisk({
driver: createS3Driver({
bucket: "my-bucket",
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
}),
});
// Same API as filesystem!
await disk.put("avatar.jpg", imageData);
const file = await disk.get("avatar.jpg");Memory Storage (Testing) โ
import { createDisk, createMemoryDriver } from "@minimajs/disk";
const driver = createMemoryDriver();
const disk = createDisk({ driver });
// Perfect for tests
await disk.put("test.txt", "Hello World");
// Clear after tests
driver.clear();Core Concepts โ
DiskFile โ
All get(), put(), copy(), and move() operations return a DiskFile instance:
const file = await disk.put("document.pdf", pdfData);
// File properties
file.href; // Storage identifier (e.g., "s3://bucket/document.pdf")
file.name; // Filename (e.g., "document.pdf")
file.size; // File size in bytes
file.type; // MIME type (e.g., "application/pdf")
file.lastModified; // Timestamp
file.metadata; // Custom metadata
// Read file content
const buffer = await file.arrayBuffer();
const text = await file.text();
const bytes = await file.bytes();
const stream = file.stream(); // ReadableStreamStorage Identifiers (href) โ
Each file has an href that uniquely identifies it within the storage system:
- Filesystem:
file:///var/uploads/avatar.jpg - S3:
s3://bucket/path/to/file.jpg - Azure:
https://account.blob.core.windows.net/container/file.jpg - Memory:
/path/to/file.jpg
API Reference โ
Disk Operations โ
put(path, data, options?) โ
Store a file by path.
const file = await disk.put("uploads/photo.jpg", imageData, {
type: "image/jpeg",
metadata: { userId: "123", album: "vacation" },
});Parameters:
path: string- File pathdata: Blob | File | string | ArrayBuffer | ReadableStream- File contentoptions?: PutOptionstype?: string- MIME typemetadata?: Record<string, string>- Custom metadatalastModified?: Date- Last modified date
Returns: Promise<DiskFile>
put(file, options?) โ
Store a File object directly. The file is stored under its original name (file.name) โ the filename is preserved as-is (file integrity).
// Stored under the file's original name
const uploaded = await disk.put(uploadedFile);
console.log(uploaded.name); // same as uploadedFile.name
// With additional options
const uploaded = await disk.put(uploadedFile, {
metadata: { userId: "123" },
});To generate unique or structured names automatically, use the storeAs plugin.
Parameters:
file: File- A Web APIFileobject (e.g., from a multipart upload)options?: PutOptions- MIME type is inferred fromfile.typeif not set
Returns: Promise<DiskFile>
get(path) โ
Retrieve a file.
const file = await disk.get("uploads/photo.jpg");
if (file) {
const data = await file.arrayBuffer();
}Parameters:
path: string- File path
Returns: Promise<DiskFile | null>
exists(path) โ
Check if a file exists.
const exists = await disk.exists("uploads/photo.jpg");Parameters:
path: string- File path
Returns: Promise<boolean>
delete(source) โ
Delete a file by path, File, or DiskFile.
// By path
await disk.delete("uploads/photo.jpg");
// By DiskFile โ uses file.href (storage identifier)
const file = await disk.get("uploads/photo.jpg");
await disk.delete(file);
// By plain File โ uses file.name as the path
await disk.delete(uploadedFile);Parameters:
source: string | File- File path,File, orDiskFile
Returns: Promise<string> โ the resolved href of the deleted file
url(path, options?) โ
Get public URL for a file.
const url = await disk.url("uploads/photo.jpg", {
expiresIn: 3600, // 1 hour (for signed URLs)
});Parameters:
path: string- File pathoptions?: UrlOptionsexpiresIn?: number- Expiration time in seconds (for signed URLs)
Returns: Promise<string>
copy(from, to) โ
Copy a file.
// From path
await disk.copy("uploads/photo.jpg", "backups/photo.jpg");
// From DiskFile
const file = await disk.get("uploads/photo.jpg");
await disk.copy(file, "backups/photo.jpg");Parameters:
from: string | File- Source file path,File, orDiskFileto: string- Destination path
Returns: Promise<DiskFile>
move(from, to) โ
Move/rename a file.
await disk.move("uploads/photo.jpg", "archive/photo.jpg");Parameters:
from: string | File- Source file path,File, orDiskFileto: string- Destination path
Returns: Promise<DiskFile>
list(prefix?, options?) โ
List files with optional prefix filtering.
for await (const file of disk.list("uploads/")) {
console.log(file.href, file.size);
}
// With limit
for await (const file of disk.list("uploads/", { limit: 10 })) {
console.log(file.name);
}Parameters:
prefix?: string- Filter by prefix (optional)options?: ListOptionslimit?: number- Maximum number of files to return
Returns: AsyncIterable<DiskFile>
metadata(path) โ
Get file metadata without downloading content.
const metadata = await disk.metadata("uploads/photo.jpg");
if (metadata) {
console.log(metadata.size, metadata.type, metadata.lastModified);
}Parameters:
path: string- File path
Returns: Promise<FileMetadata | null>
Plugins โ
Plugins extend disk behavior by hooking into file operations. Pass them as rest arguments to createDisk:
import { createDisk, storeAs, partition, atomicWrite, checksum, uploadProgress, downloadProgress } from "@minimajs/disk";
const disk = createDisk(
{ driver: createFsDriver({ root: "./uploads" }) },
storeAs("uuid"),
partition({ by: "date" }),
atomicWrite(),
checksum()
);storeAs(nameStrategy | nameGenerator) โ
Automatically rename files when a File object is passed to put. By default, put(file) preserves the original filename โ use storeAs to opt into UUID-based or custom naming.
import { storeAs } from "@minimajs/disk";
// UUID filename โ "550e8400-โฆ.jpg"
const disk = createDisk({ driver }, storeAs("uuid"));
// UUID prefix + original name โ "550e8400-โฆ-photo.jpg"
const disk = createDisk({ driver }, storeAs("uuid-original"));
// Custom generator โ full control (sync or async)
const disk = createDisk({ driver }, storeAs(file =>
`${new Date().getFullYear()}/${randomUUID()}${extname(file.name)}`
));When the name is changed, the original filename is saved in file.metadata.originalName.
| Strategy | Example output |
|---|---|
"uuid" (default) | 550e8400-โฆ.jpg |
"uuid-original" | 550e8400-โฆ-photo.jpg |
(file) => string | whatever you return |
Only applies when data instanceof File. Calls with a plain path are unaffected.
โ View all plugins
Drivers โ
| Driver | Package | Use Case |
|---|---|---|
| Filesystem | @minimajs/disk | Local / development |
| Memory | @minimajs/disk | Testing |
| AWS S3 | @minimajs/aws-s3 | Production |
| Azure Blob | @minimajs/azure-blob | Production |
- Filesystem Driver - Local file storage
- AWS S3 Driver - Amazon S3 storage
- Azure Blob Driver - Microsoft Azure Blob Storage
- Memory Driver - In-memory storage for testing
- Protocol Disk - Multi-driver routing by URL prefix
Creating Custom Drivers โ
Implement the DiskDriver interface:
import type { DiskDriver, FileMetadata, PutOptions, ListOptions, UrlOptions } from "@minimajs/disk";
class CustomDriver implements DiskDriver {
async put(href: string, stream: ReadableStream, options?: PutOptions): Promise<FileMetadata> { โฆ }
async get(href: string): Promise<[ReadableStream, FileMetadata] | null> { โฆ }
async delete(href: string): Promise<void> { โฆ }
async exists(href: string): Promise<boolean> { โฆ }
async copy(from: string, to: string): Promise<void> { โฆ }
async move(from: string, to: string): Promise<void> { โฆ }
async *list(prefix?: string, options?: ListOptions): AsyncIterable<FileMetadata> { โฆ }
async metadata(href: string): Promise<FileMetadata | null> { โฆ }
async url(href: string, options?: UrlOptions): Promise<string> { โฆ }
}Error Handling โ
import { DiskReadError, DiskWriteError, DiskFileNotFoundError, DiskMetadataError } from "@minimajs/disk";
try {
await disk.get("missing.txt");
} catch (error) {
if (error instanceof DiskFileNotFoundError) {
console.log("File not found:", error.href);
} else if (error instanceof DiskReadError) {
console.log("Failed to read:", error.href);
}
}Error Types โ
DiskError- Base error classDiskReadError- Failed to read fileDiskWriteError- Failed to write fileDiskFileNotFoundError- File not foundDiskCopyError- Copy operation failedDiskMoveError- Move operation failedDiskDeleteError- Delete operation failedDiskUrlError- URL generation failedDiskMetadataError- Metadata retrieval failedDiskConfigError- Invalid configuration