Storage
Storage gives you one file-storage interface with interchangeable backends for S3-compatible object storage, the local filesystem, and in-memory key-value caching.
In one example
DiskHelper needs no external server, so it's the fastest way to see the shape of the API. Create a bucket, upload a file, get a link back.
import { DiskHelper } from '@venizia/ignis-helpers';
const storage = new DiskHelper({ basePath: './app_data/storage' });
await storage.createBucket({ bucket: { name: 'uploads' } });
const [result] = await storage.upload({
bucket: { name: 'uploads' },
files: [
{
originalName: 'report.pdf',
mimetype: 'application/pdf',
buffer: fileBuffer,
size: fileBuffer.length,
},
],
});
console.log(result);
// {
// bucket: { name: 'uploads' },
// object: { key: 'report.pdf', size: 12345, contentType: 'application/pdf' },
// link: '/static-resources/uploads/report.pdf',
// }Every public method takes one options object with nested refs: a bucket is { name }, an object is { key }.
BunS3Helper and DiskHelper accept the same upload() call - swap the constructor, keep everything else unchanged.
How it works
BaseStorageHelperowns the shared logic. It's an abstract class implementingIStorageHelper: name/path validation, MIME type detection, and theupload()orchestration itself. Each backend only supplies two protected hooks:defaultLinkPrefixandwriteObject().- Everything else is per-backend.
hasBucket,getBuckets,createBucket,getObject,getStat,removeObject,listObjects, and the rest ofIStorageHelperare implemented independently per backend. A filesystemstat()and an S3stat()share nothing beyond the return shape. - The object-storage backends are interchangeable. Write services against
IStorageHelper, not a concrete class, and swap backends by construction only. MemoryStorageHelperis unrelated. It's a standalone generic key-value store for in-process caching, extendingBaseHelperdirectly - no bucket or file concept.- Every write path is validated first. Every key and every
folderPathruns through the path ruleisValidObjectKey()enforces, before anything touches the filesystem or object store. - The original file name is checked by what it becomes. Without
normalizeNameFnit is the key, so it must passisValidSegment(). With one, it is only metadata:Báo cáo [Q3] & tổng hợp #1!.xlsxis accepted, and only control characters, an empty name or more than 255 characters are refused. - Validation blocks four kinds of bad input: path traversal (
../), shell-injection characters, hidden files, and folder nesting beyondmaxFolderDepth(default2). - A custom
normalizeNameFndoesn't get a free pass. Its output runs through the same check, so a traversal payload smuggled back from application code is rejected too. - The S3 backend stays optional.
BunS3Helperlives behind a separate sub-path export, so an app that only needsDiskHelperorMemoryStorageHelpernever requires the Bun runtime.
Backends
| Backend | Storage | Mechanism | Import |
|---|---|---|---|
BunS3Helper | S3-compatible object storage | Bun's native S3Client (Bun runtime only) | @venizia/ignis-helpers/bun-s3 |
DiskHelper | Local filesystem, one directory per bucket | Node fs/fs/promises | @venizia/ignis-helpers |
MinioHelper (deprecated) | MinIO and other S3-compatible servers | The minio driver | @venizia/ignis-helpers/minio |
MemoryStorageHelper | In-process key-value cache | A Map | @venizia/ignis-helpers |
WARNING
MinioHelper is deprecated. Use BunS3Helper: it reaches MinIO over the same S3 API, and adds presigned URLs, object tagging and byte ranges.
Common tasks
Choose and construct a backend
import { DiskHelper } from '@venizia/ignis-helpers';
import { BunS3Helper } from '@venizia/ignis-helpers/bun-s3';
const disk = new DiskHelper({ basePath: './app_data/storage' });
// Any S3-compatible endpoint: AWS, Cloudflare R2, DigitalOcean Spaces, MinIO.
const s3 = new BunS3Helper({
endpoint: { default: 'http://localhost:9000' },
accessKey: process.env.S3_ACCESS_KEY,
secretKey: process.env.S3_SECRET_KEY,
region: 'us-east-1',
});Upload with a folder path
When folderPath is set, the default normalization creates a subdirectory-based object name.
const [result] = await storage.upload({
bucket: { name: 'uploads' },
files: [
{
originalName: 'avatar.png',
mimetype: 'image/png',
buffer: avatarBuffer,
size: avatarBuffer.length,
folderPath: 'users',
},
],
});
// result.object.key: 'users/avatar.png'Download a file
getObject() returns a Node.js Readable on every backend, so piping to a response or a write stream works identically.
const fileStream = await storage.getObject({
bucket: { name: 'uploads' },
object: { key: 'report.pdf' },
});
fileStream.pipe(response);getObjectStream() returns a web ReadableStream instead, which is what a Response body wants. It also takes an optional range.
List and delete objects
const objects = await storage.listObjects({ bucket: { name: 'uploads' }, useRecursive: true });
await storage.removeObjects({
bucket: { name: 'uploads' },
objects: objects.map(object => ({ key: object.name! })),
});Write storage-agnostic services
Depend on IStorageHelper, not a concrete class, so backends swap without touching service code.
import type { IBucketRef, IStorageHelper, IUploadFile } from '@venizia/ignis-helpers';
class FileService {
constructor(private storage: IStorageHelper) {}
uploadFile(opts: { bucket: IBucketRef; file: IUploadFile }) {
const { bucket, file } = opts;
return this.storage.upload({ bucket, files: [file] });
}
}Cache values in-memory
MemoryStorageHelper is a separate, generic key-value store - not a bucket-based backend. The value type comes from the key, so you never pass a type argument at the call site.
import { MemoryStorageHelper } from '@venizia/ignis-helpers';
const cache = MemoryStorageHelper.newInstance<{ counter: number }>();
cache.set('counter', 1);
cache.get('counter'); // 1, typed number | undefined
cache.unset('counter'); // true - it was boundSee also
- Full reference - every class, method signature, validation rule, and error message
- Static Asset Component - serving stored files over HTTP
- Request Utilities -
parseMultipartBodyfor file uploads - Helpers Index - all available helpers
- Queue Helper - message queue processing
Files:
packages/helpers/src/modules/storage/base.ts-BaseStorageHelperpackages/helpers/src/modules/storage/bun-s3/helper.ts-BunS3Helperpackages/helpers/src/modules/storage/disk/helper.ts-DiskHelperpackages/helpers/src/modules/storage/minio/helper.ts-MinioHelper(deprecated)packages/helpers/src/modules/storage/in-memory/helper.ts-MemoryStorageHelper