Development guide for the @rytass/storages base package (storage base package development guide). Use this guide when creating new storage adapters, learning the base interfaces, or extending storage functionality. It covers StorageInterface, the reusable Storage<O> base class, file converters, hash algorithms, and common implementation patterns.
This skill provides guidance for developers working with the @rytass/storages base package, including creating new storage adapters.
The @rytass/storages package defines the core interfaces and types that all storage adapters must implement. It follows the adapter pattern to provide a unified API across different storage providers.
Package: @rytass/storages (v0.2.5)
Adapters built on this base:
@rytass/storages-adapter-s3 - AWS S3@rytass/storages-adapter-gcs - Google Cloud Storage@rytass/storages-adapter-r2 - Cloudflare R2@rytass/storages-adapter-azure-blob - Azure Blob Storage@rytass/storages-adapter-local - Local File System@rytass/storages (Base Package)
│
├── StorageInterface # Core interface all adapters must implement
├── Storage<O> # Base class with helper methods (not abstract)
├── ConverterManager # File converter pipeline system (from @rytass/file-converter)
├── Types & Interfaces # Shared type definitions
└── Error Handling # StorageError, ErrorCode enums
@rytass/storages-adapter-* # Provider implementations
│
├── [Provider]Storage # Extends Storage<ProviderOptions>
├── typings.ts # Provider-specific option types
└── index.ts # Package exports
Buffer | Readable - Files to upload can be either in-memory buffers or streams{ key: string } - Uploaded file reference containing the storage key/pathnpm install @rytass/storages
StorageInterface - Core interface defining the storage contract:
interface StorageInterface {
// Upload operations (Note: options are NOT part of the interface)
write(file: InputFile): Promise<StorageFile>;
batchWrite(files: InputFile[]): Promise<StorageFile[]>;
// Download operations
read(key: string): Promise<Readable>;
read(key: string, options: ReadBufferFileOptions): Promise<Buffer>;
read(key: string, options: ReadStreamFileOptions): Promise<Readable>;
// File management
remove(key: string): Promise<void>;
// Note: isExists() is NOT part of the interface, it's in Storage class
}
Storage<O> Class - Base implementation with helper methods (not abstract, uses throw to force override):
class Storage<O extends Record<string, unknown> = Record<string, unknown>>
implements StorageInterface {
// Provided by base class
readonly converterManager: ConverterManager;
readonly hashAlgorithm: FilenameHashAlgorithm;
constructor(options?: StorageOptions<O>);
// File type detection helpers
getExtension(file: InputFile): Promise<FileTypeResult | undefined>;
getBufferFilename(buffer: Buffer): Promise<[string, string | undefined]>;
getStreamFilename(stream: Readable): Promise<[string, string | undefined]>;
// Methods to override (throw Error by default, subclasses must override)
write(file: InputFile, options?: WriteFileOptions): Promise<StorageFile>;
batchWrite(files: InputFile[], options?: WriteFileOptions[]): Promise<StorageFile[]>;
read(key: string): Promise<Readable>;
read(key: string, options: ReadBufferFileOptions): Promise<Buffer>;
read(key: string, options: ReadStreamFileOptions): Promise<Readable>;
remove(key: string): Promise<void>;
// Additional method to override (NOT in StorageInterface)
isExists(key: string): Promise<boolean>;
}
Note: The
Storageclass is NOT abstract. Instead, methods throwError('Method not implemented.')by default, requiring subclasses to override them. ThewriteandbatchWritemethods acceptoptionsparameter in the implementation but NOT inStorageInterface.
All adapters extending Storage<O> MUST override these methods (not abstract, but throw by default):
| Method | Source | Description |
|--------|--------|-------------|
| write(file, options?) | Storage class (options not in interface) | Upload a single file and return storage key |
| batchWrite(files, options?) | Storage class (options not in interface) | Upload multiple files in parallel |
| read(key, options?) | StorageInterface | Download file as Buffer or Stream |
| remove(key) | StorageInterface | Delete a file |
| isExists(key) | Storage class only | Check if file exists (not in interface) |
Note:
isExists()is defined in theStorageclass but NOT inStorageInterface. This means adapters must implement it, but code depending only onStorageInterfacecannot assume it exists. Similarly, theoptionsparameter forwriteandbatchWriteis only in theStorageclass implementation.
Adapters MAY implement these additional methods:
| Method | Description | Example |
|--------|-------------|---------|
| url(key, options?) | Generate presigned/signed URL for temporary access | Cloud adapters (S3, GCS, R2, Azure) |
| Custom helpers | Provider-specific utilities | getUsageInfo() in Local adapter |
// Input/Output Types (from @rytass/file-converter)
type ConvertableFile = Readable | Buffer;
type InputFile = ConvertableFile; // Re-exported alias
type FileKey = string;
interface StorageFile {
readonly key: FileKey;
}
// Options Types
interface StorageOptions<O extends Record<string, unknown>> {
converters?: FileConverter<O>[];
hashAlgorithm?: 'sha1' | 'sha256';
}
interface WriteFileOptions {
filename?: string; // Custom filename (overrides hash-based generation)
contentType?: string; // MIME type for the file
}
// Read Format Options
interface ReadBufferFileOptions {
format: 'buffer';
}
interface ReadStreamFileOptions {
format: 'stream';
}
enum ErrorCode {
WRITE_FILE_ERROR = '101', // Failed to upload file
READ_FILE_ERROR = '102', // Failed to download file
REMOVE_FILE_ERROR = '103', // Failed to delete file
UNRECOGNIZED_ERROR = '104', // Unknown error
DIRECTORY_NOT_FOUND = '201', // Directory doesn't exist (Local adapter)
FILE_NOT_FOUND = '202', // File doesn't exist
}
When implementing a new storage adapter, you are responsible for:
Storage<YourOptions> - Inherit from the base classgetBufferFilename() / getStreamFilename()converterManager.convert() before uploadStorageError with correct ErrorCodeThe base package includes a converter system for processing files during upload:
// From @rytass/file-converter
type ConvertableFile = Readable | Buffer;
interface FileConverter<O = Record<string, unknown>> {
convert<Buffer>(file: ConvertableFile): Promise<Buffer>;
convert<Readable>(file: ConvertableFile): Promise<Readable>;
}
class ConverterManager {
constructor(converters: FileConverter[]);
convert<ConvertableFileFormat extends ConvertableFile>(file: ConvertableFile): Promise<ConvertableFileFormat>;
}
// Usage in adapter
const convertedFile = await this.converterManager.convert(inputFile);
Example converters:
Converters are executed in sequence before the file is uploaded to the storage provider.
For complete interface specifications and step-by-step implementation guide:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer