Storage -- API Reference
Architecture
The storage system uses a class hierarchy with an abstract base class providing shared logic and two concrete implementations for different backends. MemoryStorageHelper is a separate, standalone class that does not participate in the IStorageHelper hierarchy.
BaseHelper
├── BaseStorageHelper (abstract, implements IStorageHelper)
│ ├── MinioHelper -- S3-compatible object storage (minio SDK)
│ ├── BunS3Helper -- S3-compatible object storage (Bun-native S3Client)
│ └── DiskHelper -- Local filesystem storage
└── MemoryStorageHelper -- In-memory key-value storeBaseStorageHelper
Abstract base class that extends BaseHelper and implements IStorageHelper. Provides name validation, MIME type detection, and file type categorization. All bucket and file operation methods are abstract and must be implemented by subclasses.
Constructor
constructor(opts: { scope: string; identifier: string })| Parameter | Type | Description |
|---|---|---|
scope | string | Logger scope name. |
identifier | string | Helper identifier. |
Static Properties
MIME_MAP
protected static MIME_MAP: Record<string, string>Extension-to-MIME-type mapping used by getMimeType():
| Extension | MIME Type |
|---|---|
.png | image/png |
.jpg, .jpeg | image/jpeg |
.gif | image/gif |
.webp | image/webp |
.svg | image/svg+xml |
.pdf | application/pdf |
.json | application/json |
.txt | text/plain |
.html | text/html |
.css | text/css |
.js | text/javascript |
.mp4 | video/mp4 |
.webm | video/webm |
.mp3 | audio/mpeg |
.wav | audio/wav |
.zip | application/zip |
.csv | text/csv |
.xml | application/xml |
Falls back to application/octet-stream for unrecognized extensions.
Methods
getMimeType
getMimeType(filename: string): stringReturns the MIME type for a filename based on its extension. Extracts the extension using path.extname(), converts to lowercase, and looks it up in MIME_MAP.
| Parameter | Type | Description |
|---|---|---|
filename | string | Filename with extension (e.g., 'photo.jpg'). |
Returns: MIME type string, or 'application/octet-stream' if unrecognized.
isValidName
isValidName(name: string): booleanValidates a bucket or file name against security rules. Logs specific error messages for each validation failure.
| Parameter | Type | Description |
|---|---|---|
name | string | Name to validate. |
Returns: true if the name passes all checks, false otherwise.
Validation rules (checked in order):
- Must be a string type
- Must not be empty or null
- Must not contain
..,/, or\(path traversal) - Must not start with
.(hidden files) - Must not contain
;,|,&,$,`,<,>,{,},[,],!,#(shell injection) - Must not contain
\n,\r, or\0(header injection) - Must not exceed 255 characters (DoS prevention)
- Must not be whitespace-only
getFileType
getFileType(opts: { mimeType: string }): stringCategorizes a MIME type into a broad file type group using the MimeTypes constants.
| Parameter | Type | Description |
|---|---|---|
opts.mimeType | string | Full MIME type string (e.g., 'image/png'). |
Returns: One of 'image', 'video', 'text', or 'unknown'.
Abstract Methods
The following methods are declared abstract in BaseStorageHelper and implemented by MinioHelper, BunS3Helper, and DiskHelper:
abstract isBucketExists(opts: { name: string }): Promise<boolean>;
abstract getBuckets(): Promise<IBucketInfo[]>;
abstract getBucket(opts: { name: string }): Promise<IBucketInfo | null>;
abstract createBucket(opts: { name: string }): Promise<IBucketInfo | null>;
abstract removeBucket(opts: { name: string }): Promise<boolean>;
abstract upload(opts: {
bucket: string;
files: IUploadFile[];
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
}): Promise<IUploadResult[]>;
abstract getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
abstract getStat(opts: { bucket: string; name: string }): Promise<IFileStat>;
abstract removeObject(opts: { bucket: string; name: string }): Promise<void>;
abstract removeObjects(opts: { bucket: string; names: string[] }): Promise<void>;
abstract listObjects(opts: {
bucket: string;
prefix?: string;
useRecursive?: boolean;
maxKeys?: number;
}): Promise<IObjectInfo[]>;MinioHelper
S3-compatible object storage client built on the minio package. Extends BaseStorageHelper.
Constructor
constructor(options: IMinioHelperOptions)Creates a new minio.Client internally and stores it as this.client.
interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}| Parameter | Type | Default | Description |
|---|---|---|---|
options.endPoint | string | -- | MinIO server hostname. |
options.port | number | -- | Server port. |
options.useSSL | boolean | -- | Enable HTTPS. |
options.accessKey | string | -- | Access key credential. |
options.secretKey | string | -- | Secret key credential. |
options.scope | string | 'MinioHelper' | Logger scope name. |
options.identifier | string | 'MinioHelper' | Helper identifier. |
All additional minio.ClientOptions properties are also accepted and passed to the underlying client.
Methods
isBucketExists
async isBucketExists(opts: { name: string }): Promise<boolean>Returns false if the name fails isValidName(). Otherwise delegates to client.bucketExists().
getBuckets
async getBuckets(): Promise<IBucketInfo[]>Lists all buckets via client.listBuckets().
getBucket
async getBucket(opts: { name: string }): Promise<IBucketInfo | null>Returns the bucket info if it exists, or null if not found. Calls isBucketExists() first, then searches the full bucket list.
createBucket
async createBucket(opts: { name: string }): Promise<IBucketInfo | null>Creates a bucket via client.makeBucket(). Throws if the name fails validation.
Throws: '[createBucket] Invalid name to create bucket!'
removeBucket
async removeBucket(opts: { name: string }): Promise<boolean>Removes a bucket via client.removeBucket(). Throws if the name fails validation.
Throws: '[removeBucket] Invalid name to remove bucket!'
upload
async upload(opts: {
bucket: string;
files: IUploadFile[];
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
}): Promise<IUploadResult[]>Uploads files to a MinIO bucket. Returns [] if files is empty. Validates the bucket exists and all file names/sizes before uploading. Uploads run in parallel via Promise.all().
Default name normalization: Lowercased with spaces replaced by _. If folderPath is set, prepends {folderPath}/.
Default link format: /static-assets/{bucket}/{encodeURIComponent(normalizeName)}
Metadata stored: originalName, normalizeName, size, encoding, mimeType.
Throws:
'[upload] Bucket does not exist | name: {bucket}''[upload] Invalid original file name''[upload] Invalid folder path''[upload] Invalid file size'
getFile
getFile(opts: {
bucket: string;
name: string;
options?: {
versionId?: string;
SSECustomerAlgorithm?: string;
SSECustomerKey?: string;
SSECustomerKeyMD5?: string;
};
}): Promise<Readable>Returns a readable stream for the file via client.getObject(). Supports versioning and server-side encryption options.
| Parameter | Type | Description |
|---|---|---|
opts.bucket | string | Bucket name. |
opts.name | string | Object name. |
opts.options.versionId | string | Specific version to retrieve. |
opts.options.SSECustomerAlgorithm | string | SSE-C algorithm (e.g., 'AES256'). |
opts.options.SSECustomerKey | string | SSE-C encryption key. |
opts.options.SSECustomerKeyMD5 | string | MD5 hash of the encryption key. |
getStat
async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>Returns file metadata via client.statObject().
Returns: IFileStat with size, metadata (from MinIO's metaData), lastModified, etag, and versionId.
removeObject
async removeObject(opts: { bucket: string; name: string }): Promise<void>Removes a single object via client.removeObject().
removeObjects
async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>Removes multiple objects in a single batch via client.removeObjects().
listObjects
async listObjects(opts: {
bucket: string;
prefix?: string;
useRecursive?: boolean;
maxKeys?: number;
}): Promise<IObjectInfo[]>Lists objects in a bucket using a streaming approach via client.listObjects(). The stream is destroyed early if maxKeys is reached.
| Parameter | Type | Default | Description |
|---|---|---|---|
opts.bucket | string | -- | Bucket to list. |
opts.prefix | string | '' | Filter by prefix. |
opts.useRecursive | boolean | false | List recursively through subdirectories. |
opts.maxKeys | number | undefined | Maximum number of objects to return. |
BunS3Helper
S3-compatible object storage using Bun's native S3Client. Extends BaseStorageHelper. Requires the Bun runtime.
Bucket management operations (list, create, delete) use AWS Signature V4 signed requests built from scratch via the buildSignedRequest utility. Object operations (upload, get, stat, delete, list) use Bun's native S3 API.
Constructor
constructor(options: IBunS3HelperOptions)Creates a Bun S3Client internally and stores credentials for bucket management requests.
interface IBunS3HelperOptions extends IStorageHelperOptions {
accessKey: string;
secretKey: string;
endpoint: string;
region?: string;
sessionToken?: string;
}| Parameter | Type | Default | Description |
|---|---|---|---|
options.accessKey | string | -- | S3 access key credential. |
options.secretKey | string | -- | S3 secret key credential. |
options.endpoint | string | -- | S3-compatible endpoint URL. |
options.region | string | 'us-east-1' | AWS region for signing. |
options.sessionToken | string | -- | Optional session token for temporary credentials. |
options.scope | string | 'BunS3Helper' | Logger scope name. |
options.identifier | string | 'BunS3Helper' | Helper identifier. |
Methods
isBucketExists
async isBucketExists(opts: { name: string }): Promise<boolean>Returns false if the name fails isValidName(). Otherwise attempts a list({ maxKeys: 1 }) against the bucket. Returns false on any error.
getBuckets
async getBuckets(): Promise<IBucketInfo[]>Lists all buckets via a signed GET / request. Parses the XML response to extract bucket names and creation dates.
getBucket
async getBucket(opts: { name: string }): Promise<IBucketInfo | null>Returns the bucket info from the full bucket list, or null if not found.
createBucket
async createBucket(opts: { name: string }): Promise<IBucketInfo | null>Creates a bucket via a signed PUT /{name} request. Throws if the name fails validation or the S3 request fails.
removeBucket
async removeBucket(opts: { name: string }): Promise<boolean>Removes a bucket via a signed DELETE /{name} request. Throws if the name fails validation or the S3 request fails.
upload
async upload(opts: {
bucket: string;
files: IUploadFile[];
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
}): Promise<IUploadResult[]>Uploads files using client.write(name, buffer, { bucket, type }). Same validation and normalization behavior as MinioHelper.
Default link format: /static-assets/{bucket}/{encodeURIComponent(normalizeName)}
getFile
async getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>Returns a Readable stream by converting the Bun S3 file's web stream via Readable.fromWeb().
getStat
async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>Returns file metadata via client.stat().
Returns:
{
size: number;
lastModified: Date;
metadata: {
contentType: string;
mimetype: string;
};
etag: string;
}removeObject
async removeObject(opts: { bucket: string; name: string }): Promise<void>Removes a single object via client.delete().
removeObjects
async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>Removes multiple objects in parallel via Promise.all().
listObjects
async listObjects(opts: {
bucket: string;
prefix?: string;
useRecursive?: boolean;
maxKeys?: number;
}): Promise<IObjectInfo[]>Lists objects using client.list(). Returns objects with name (from key), size, lastModified, and etag (from eTag).
NOTE
The useRecursive parameter is accepted for interface compatibility but is not used -- Bun's S3 list() does not support recursive mode directly.
DiskHelper
Local filesystem storage using directory-based buckets. Extends BaseStorageHelper.
Constructor
constructor(options: IDiskHelperOptions)Resolves basePath to an absolute path and creates it if it does not exist.
interface IDiskHelperOptions extends IStorageHelperOptions {
basePath: string;
}| Parameter | Type | Default | Description |
|---|---|---|---|
options.basePath | string | -- | Base directory for storage. Resolved to an absolute path. Created automatically. |
options.scope | string | 'DiskHelper' | Logger scope name. |
options.identifier | string | 'DiskHelper' | Helper identifier. |
Methods
isBucketExists
async isBucketExists(opts: { name: string }): Promise<boolean>Returns false if the name fails validation. Otherwise checks if the bucket path exists and is a directory.
getBuckets
async getBuckets(): Promise<IBucketInfo[]>Lists all directories under basePath. Returns each directory as a bucket with its birthtime as creationDate. Returns [] if the base path does not exist.
getBucket
async getBucket(opts: { name: string }): Promise<IBucketInfo | null>Returns bucket info with birthtime as creationDate, or null if the bucket does not exist.
createBucket
async createBucket(opts: { name: string }): Promise<IBucketInfo | null>Creates a directory under basePath. Throws if the name fails validation or the bucket already exists.
Throws:
'[createBucket] Invalid name to create bucket!''[createBucket] Bucket already exists | name: {name}'
removeBucket
async removeBucket(opts: { name: string }): Promise<boolean>Removes the bucket directory. Throws if the name fails validation, the bucket does not exist, or the bucket is not empty.
Throws:
'[removeBucket] Invalid name to remove bucket!''[removeBucket] Bucket does not exist | name: {name}''[removeBucket] Bucket is not empty | name: {name}'
upload
async upload(opts: {
bucket: string;
files: IUploadFile[];
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
}): Promise<IUploadResult[]>Writes files to the bucket directory. Returns [] if files is empty. Validates the bucket exists and all file names/sizes before writing. Uploads run in parallel via Promise.all(). Subdirectories are created automatically if normalizeName contains path separators.
Default name normalization: Same as MinioHelper -- lowercased with spaces replaced by _.
Default link format: /static-resources/{bucket}/{encodeURIComponent(normalizeName)}
Throws:
'[upload] Bucket does not exist | name: {bucket}''[upload] Invalid original file name''[upload] Invalid folder path''[upload] Invalid file size'
getFile
async getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>Returns a fs.createReadStream() for the file. Throws if the file does not exist. The options parameter is accepted for interface compatibility but is not used.
Throws: '[getFile] File not found | bucket: {bucket} | name: {name}'
getStat
async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>Returns file metadata from the filesystem. The metadata field contains mimetype detected via getMimeType(). Does not return etag or versionId.
Throws: '[getStat] File not found | bucket: {bucket} | name: {name}'
Returns:
{
size: number; // from fs stat
lastModified: Date; // from fs stat mtime
metadata: {
mimetype: string; // detected via getMimeType()
};
}removeObject
async removeObject(opts: { bucket: string; name: string }): Promise<void>Deletes a file via fsp.unlink(). Throws if the file does not exist.
Throws: '[removeObject] File not found | bucket: {bucket} | name: {name}'
removeObjects
async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>Deletes multiple files sequentially by calling removeObject() for each name. If any file does not exist, the error propagates immediately.
listObjects
async listObjects(opts: {
bucket: string;
prefix?: string;
useRecursive?: boolean;
maxKeys?: number;
}): Promise<IObjectInfo[]>Scans the bucket directory. Returns [] if the bucket path does not exist. Only files matching the prefix are included. Subdirectories are only traversed when useRecursive is true. Stops scanning when maxKeys is reached.
| Parameter | Type | Default | Description |
|---|---|---|---|
opts.bucket | string | -- | Bucket to list. |
opts.prefix | string | '' | Filter by name prefix. |
opts.useRecursive | boolean | false | Traverse subdirectories. |
opts.maxKeys | number | undefined | Maximum objects to return. |
Returns: Array of IObjectInfo with name, size, lastModified. The etag field is always undefined for disk storage.
MemoryStorageHelper
Generic in-memory key-value store. Extends BaseHelper directly. Does not implement IStorageHelper.
class MemoryStorageHelper<T extends object = AnyObject> extends BaseHelperConstructor
constructor(opts?: { scope?: string })| Parameter | Type | Default | Description |
|---|---|---|---|
opts.scope | string | 'MemoryStorageHelper' | Logger scope name. |
Static Methods
newInstance
static newInstance<T extends object = AnyObject>(): MemoryStorageHelper<T>Factory method that creates and returns a new MemoryStorageHelper instance.
Methods
isBound
isBound(key: string): booleanReturns true if the key exists in the container (uses the in operator).
get
get<R>(key: keyof T): RReturns the value for the given key, cast to type R.
set
set<R>(key: string, value: R): voidStores a value under the given key using Object.assign.
keys
keys(): string[]Returns all keys in the container via Object.keys().
clear
clear(): voidReplaces the container with a new empty object.
getContainer
getContainer(): TReturns the underlying container object.
Types Reference
IStorageHelper
The unified interface implemented by MinioHelper, BunS3Helper, and DiskHelper:
interface IStorageHelper {
isValidName(name: string): boolean;
isBucketExists(opts: { name: string }): Promise<boolean>;
getBuckets(): Promise<IBucketInfo[]>;
getBucket(opts: { name: string }): Promise<IBucketInfo | null>;
createBucket(opts: { name: string }): Promise<IBucketInfo | null>;
removeBucket(opts: { name: string }): Promise<boolean>;
upload(opts: {
bucket: string;
files: IUploadFile[];
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
}): Promise<IUploadResult[]>;
getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
getStat(opts: { bucket: string; name: string }): Promise<IFileStat>;
removeObject(opts: { bucket: string; name: string }): Promise<void>;
removeObjects(opts: { bucket: string; names: string[] }): Promise<void>;
listObjects(opts: IListObjectsOptions): Promise<IObjectInfo[]>;
getFileType(opts: { mimeType: string }): string;
}IStorageHelperOptions
interface IStorageHelperOptions {
scope?: string;
identifier?: string;
}IUploadFile
interface IUploadFile {
originalName: string; // Original filename
mimetype: string; // MIME type (e.g., 'image/png')
buffer: Buffer; // File content
size: number; // File size in bytes
encoding?: string; // Optional encoding (e.g., '7bit', 'base64')
folderPath?: string; // Optional folder path for organization
[key: string | symbol]: any; // Additional properties allowed
}IUploadResult
interface IUploadResult {
bucketName: string; // Bucket where file was stored
objectName: string; // Stored filename (normalized)
link: string; // Access URL
metaLink?: any; // Optional metadata link
metaLinkError?: any; // Error if metadata link creation failed
}IFileStat
interface IFileStat {
size: number; // File size in bytes
metadata: Record<string, any>; // Storage-specific metadata
lastModified?: Date; // Last modification date
etag?: string; // Entity tag (MinioHelper only)
versionId?: string; // Version ID (MinioHelper only)
}IBucketInfo
interface IBucketInfo {
name: string; // Bucket name
creationDate: Date; // When the bucket was created
}IObjectInfo
interface IObjectInfo {
name?: string; // Object name
size?: number; // Object size in bytes
lastModified?: Date; // Last modification date
etag?: string; // Entity tag
prefix?: string; // Prefix (for directory-like listing)
}IListObjectsOptions
interface IListObjectsOptions {
bucket: string; // Bucket to list
prefix?: string; // Filter by prefix
useRecursive?: boolean; // Recursive listing (default: false)
maxKeys?: number; // Maximum objects to return
}IDiskHelperOptions
interface IDiskHelperOptions extends IStorageHelperOptions {
basePath: string; // Base directory for storage
}IMinioHelperOptions
interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}Inherits all minio.ClientOptions properties: endPoint, port, useSSL, accessKey, secretKey, region, transport, sessionToken, partSize, pathStyle, and others.
IBunS3HelperOptions
interface IBunS3HelperOptions extends IStorageHelperOptions {
accessKey: string;
secretKey: string;
endpoint: string;
region?: string; // Default: 'us-east-1'
sessionToken?: string;
}See Also
- Setup & Usage -- Getting started, examples, and troubleshooting
- Helpers Index -- All available helpers
- Static Asset Component -- Serving stored files via HTTP