Loading...
Loading...
Development guide for @rytass/storages base package (儲存基底套件開發指南). Use when creating new storage adapters (新增儲存 adapter), understanding base interfaces, or extending storage functionality. Covers StorageInterface, Storage class, file converters (檔案轉換器), hash algorithms (雜湊演算法), and implementation patterns.
npx skill4agent add rytass/utils storage-development@rytass/storages@rytass/storages@rytass/storages@rytass/storages-adapter-s3@rytass/storages-adapter-gcs@rytass/storages-adapter-r2@rytass/storages-adapter-azure-blob@rytass/storages-adapter-local@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 exportsBuffer | Readable{ key: string }npm install @rytass/storagesStorageInterfaceinterface 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 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: Theclass is NOT abstract. Instead, methods throwStorageby default, requiring subclasses to override them. TheError('Method not implemented.')andwritemethods acceptbatchWriteparameter in the implementation but NOT inoptions.StorageInterface
Storage<O>| Method | Source | Description |
|---|---|---|
| Storage class (options not in interface) | Upload a single file and return storage key |
| Storage class (options not in interface) | Upload multiple files in parallel |
| StorageInterface | Download file as Buffer or Stream |
| StorageInterface | Delete a file |
| Storage class only | Check if file exists (not in interface) |
Note:is defined in theisExists()class but NOT inStorage. This means adapters must implement it, but code depending only onStorageInterfacecannot assume it exists. Similarly, theStorageInterfaceparameter foroptionsandwriteis only in thebatchWriteclass implementation.Storage
| Method | Description | Example |
|---|---|---|
| Generate presigned/signed URL for temporary access | Cloud adapters (S3, GCS, R2, Azure) |
| Custom helpers | Provider-specific utilities | |
// 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
}Storage<YourOptions>getBufferFilename()getStreamFilename()converterManager.convert()StorageErrorErrorCode// 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);