Skip to content

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 store

BaseStorageHelper

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

typescript
constructor(opts: { scope: string; identifier: string })
ParameterTypeDescription
scopestringLogger scope name.
identifierstringHelper identifier.

Static Properties

MIME_MAP

typescript
protected static MIME_MAP: Record<string, string>

Extension-to-MIME-type mapping used by getMimeType():

ExtensionMIME Type
.pngimage/png
.jpg, .jpegimage/jpeg
.gifimage/gif
.webpimage/webp
.svgimage/svg+xml
.pdfapplication/pdf
.jsonapplication/json
.txttext/plain
.htmltext/html
.csstext/css
.jstext/javascript
.mp4video/mp4
.webmvideo/webm
.mp3audio/mpeg
.wavaudio/wav
.zipapplication/zip
.csvtext/csv
.xmlapplication/xml

Falls back to application/octet-stream for unrecognized extensions.

Methods

getMimeType

typescript
getMimeType(filename: string): string

Returns 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.

ParameterTypeDescription
filenamestringFilename with extension (e.g., 'photo.jpg').

Returns: MIME type string, or 'application/octet-stream' if unrecognized.

isValidName

typescript
isValidName(name: string): boolean

Validates a bucket or file name against security rules. Logs specific error messages for each validation failure.

ParameterTypeDescription
namestringName to validate.

Returns: true if the name passes all checks, false otherwise.

Validation rules (checked in order):

  1. Must be a string type
  2. Must not be empty or null
  3. Must not contain .., /, or \ (path traversal)
  4. Must not start with . (hidden files)
  5. Must not contain ;, |, &, $, `, <, >, {, }, [, ], !, # (shell injection)
  6. Must not contain \n, \r, or \0 (header injection)
  7. Must not exceed 255 characters (DoS prevention)
  8. Must not be whitespace-only

getFileType

typescript
getFileType(opts: { mimeType: string }): string

Categorizes a MIME type into a broad file type group using the MimeTypes constants.

ParameterTypeDescription
opts.mimeTypestringFull 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:

typescript
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

typescript
constructor(options: IMinioHelperOptions)

Creates a new minio.Client internally and stores it as this.client.

typescript
interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}
ParameterTypeDefaultDescription
options.endPointstring--MinIO server hostname.
options.portnumber--Server port.
options.useSSLboolean--Enable HTTPS.
options.accessKeystring--Access key credential.
options.secretKeystring--Secret key credential.
options.scopestring'MinioHelper'Logger scope name.
options.identifierstring'MinioHelper'Helper identifier.

All additional minio.ClientOptions properties are also accepted and passed to the underlying client.

Methods

isBucketExists

typescript
async isBucketExists(opts: { name: string }): Promise<boolean>

Returns false if the name fails isValidName(). Otherwise delegates to client.bucketExists().

getBuckets

typescript
async getBuckets(): Promise<IBucketInfo[]>

Lists all buckets via client.listBuckets().

getBucket

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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.

ParameterTypeDescription
opts.bucketstringBucket name.
opts.namestringObject name.
opts.options.versionIdstringSpecific version to retrieve.
opts.options.SSECustomerAlgorithmstringSSE-C algorithm (e.g., 'AES256').
opts.options.SSECustomerKeystringSSE-C encryption key.
opts.options.SSECustomerKeyMD5stringMD5 hash of the encryption key.

getStat

typescript
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

typescript
async removeObject(opts: { bucket: string; name: string }): Promise<void>

Removes a single object via client.removeObject().

removeObjects

typescript
async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>

Removes multiple objects in a single batch via client.removeObjects().

listObjects

typescript
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.

ParameterTypeDefaultDescription
opts.bucketstring--Bucket to list.
opts.prefixstring''Filter by prefix.
opts.useRecursivebooleanfalseList recursively through subdirectories.
opts.maxKeysnumberundefinedMaximum 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

typescript
constructor(options: IBunS3HelperOptions)

Creates a Bun S3Client internally and stores credentials for bucket management requests.

typescript
interface IBunS3HelperOptions extends IStorageHelperOptions {
  accessKey: string;
  secretKey: string;
  endpoint: string;
  region?: string;
  sessionToken?: string;
}
ParameterTypeDefaultDescription
options.accessKeystring--S3 access key credential.
options.secretKeystring--S3 secret key credential.
options.endpointstring--S3-compatible endpoint URL.
options.regionstring'us-east-1'AWS region for signing.
options.sessionTokenstring--Optional session token for temporary credentials.
options.scopestring'BunS3Helper'Logger scope name.
options.identifierstring'BunS3Helper'Helper identifier.

Methods

isBucketExists

typescript
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

typescript
async getBuckets(): Promise<IBucketInfo[]>

Lists all buckets via a signed GET / request. Parses the XML response to extract bucket names and creation dates.

getBucket

typescript
async getBucket(opts: { name: string }): Promise<IBucketInfo | null>

Returns the bucket info from the full bucket list, or null if not found.

createBucket

typescript
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

typescript
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

typescript
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

typescript
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

typescript
async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>

Returns file metadata via client.stat().

Returns:

typescript
{
  size: number;
  lastModified: Date;
  metadata: {
    contentType: string;
    mimetype: string;
  };
  etag: string;
}

removeObject

typescript
async removeObject(opts: { bucket: string; name: string }): Promise<void>

Removes a single object via client.delete().

removeObjects

typescript
async removeObjects(opts: { bucket: string; names: string[] }): Promise<void>

Removes multiple objects in parallel via Promise.all().

listObjects

typescript
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

typescript
constructor(options: IDiskHelperOptions)

Resolves basePath to an absolute path and creates it if it does not exist.

typescript
interface IDiskHelperOptions extends IStorageHelperOptions {
  basePath: string;
}
ParameterTypeDefaultDescription
options.basePathstring--Base directory for storage. Resolved to an absolute path. Created automatically.
options.scopestring'DiskHelper'Logger scope name.
options.identifierstring'DiskHelper'Helper identifier.

Methods

isBucketExists

typescript
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

typescript
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

typescript
async getBucket(opts: { name: string }): Promise<IBucketInfo | null>

Returns bucket info with birthtime as creationDate, or null if the bucket does not exist.

createBucket

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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:

typescript
{
  size: number;          // from fs stat
  lastModified: Date;    // from fs stat mtime
  metadata: {
    mimetype: string;    // detected via getMimeType()
  };
}

removeObject

typescript
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

typescript
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

typescript
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.

ParameterTypeDefaultDescription
opts.bucketstring--Bucket to list.
opts.prefixstring''Filter by name prefix.
opts.useRecursivebooleanfalseTraverse subdirectories.
opts.maxKeysnumberundefinedMaximum 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.

typescript
class MemoryStorageHelper<T extends object = AnyObject> extends BaseHelper

Constructor

typescript
constructor(opts?: { scope?: string })
ParameterTypeDefaultDescription
opts.scopestring'MemoryStorageHelper'Logger scope name.

Static Methods

newInstance

typescript
static newInstance<T extends object = AnyObject>(): MemoryStorageHelper<T>

Factory method that creates and returns a new MemoryStorageHelper instance.

Methods

isBound

typescript
isBound(key: string): boolean

Returns true if the key exists in the container (uses the in operator).

get

typescript
get<R>(key: keyof T): R

Returns the value for the given key, cast to type R.

set

typescript
set<R>(key: string, value: R): void

Stores a value under the given key using Object.assign.

keys

typescript
keys(): string[]

Returns all keys in the container via Object.keys().

clear

typescript
clear(): void

Replaces the container with a new empty object.

getContainer

typescript
getContainer(): T

Returns the underlying container object.

Types Reference

IStorageHelper

The unified interface implemented by MinioHelper, BunS3Helper, and DiskHelper:

typescript
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

typescript
interface IStorageHelperOptions {
  scope?: string;
  identifier?: string;
}

IUploadFile

typescript
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

typescript
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

typescript
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

typescript
interface IBucketInfo {
  name: string;          // Bucket name
  creationDate: Date;    // When the bucket was created
}

IObjectInfo

typescript
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

typescript
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

typescript
interface IDiskHelperOptions extends IStorageHelperOptions {
  basePath: string;    // Base directory for storage
}

IMinioHelperOptions

typescript
interface IMinioHelperOptions extends IStorageHelperOptions, ClientOptions {}

Inherits all minio.ClientOptions properties: endPoint, port, useSSL, accessKey, secretKey, region, transport, sessionToken, partSize, pathStyle, and others.

IBunS3HelperOptions

typescript
interface IBunS3HelperOptions extends IStorageHelperOptions {
  accessKey: string;
  secretKey: string;
  endpoint: string;
  region?: string;         // Default: 'us-east-1'
  sessionToken?: string;
}

See Also