@teliagen/plugin-storage
v0.4.0
Published
Unified storage plugin for Teliagen Framework
Readme
@teliagen/plugin-storage
File storage plugin for the Teliagen Framework.
Overview
@teliagen/plugin-storage provides unified file storage operations:
- Multiple Drivers – Local filesystem, AWS S3, MinIO
- Disk Abstraction – Configure multiple storage "disks"
- File Actions – Upload, download, delete, list files
- Stream Support – Efficient large file handling
- URL Generation – Public and signed URLs
- MIME Type Detection – Automatic content type handling
Installation
npm install @teliagen/plugin-storage
# or
pnpm add @teliagen/plugin-storageFor S3/MinIO:
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presignerQuick Start
import { TeliagenApp } from '@teliagen/server';
import { StoragePlugin } from '@teliagen/plugin-storage';
const app = new TeliagenApp();
await app.initialize();
const storagePlugin = StoragePlugin({
name: 'storage',
default: 'local',
disks: {
local: {
driver: 'local',
root: './uploads',
publicPath: '/files'
}
}
});
await app.registerPlugins([storagePlugin]);
await app.start();Configuration
interface StoragePluginOptions {
name: string; // Plugin instance name
default: string; // Default disk name
disks: Record<string, DiskConfig>;
}
// Local Disk
interface LocalDiskConfig {
driver: 'local';
root: string; // Directory path
publicPath?: string; // URL prefix for serving
}
// S3 Disk
interface S3DiskConfig {
driver: 's3';
bucket: string;
region: string;
accessKeyId: string;
secretAccessKey: string;
endpoint?: string; // For S3-compatible (MinIO, DigitalOcean, etc.)
forcePathStyle?: boolean; // Required for MinIO
publicUrl?: string; // Base URL for public files
}
// MinIO Disk
interface MinioDiskConfig {
driver: 'minio';
bucket: string;
endpoint: string; // e.g., 'http://localhost:9000'
accessKey: string;
secretKey: string;
useSSL?: boolean;
publicUrl?: string;
}Complete Example
const storagePlugin = StoragePlugin({
name: 'storage',
default: 'local',
disks: {
// Local filesystem
local: {
driver: 'local',
root: './uploads',
publicPath: '/uploads'
},
// AWS S3
s3: {
driver: 's3',
bucket: 'my-app-files',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
// DigitalOcean Spaces
spaces: {
driver: 's3',
bucket: 'my-space',
region: 'nyc3',
endpoint: 'https://nyc3.digitaloceanspaces.com',
accessKeyId: process.env.DO_ACCESS_KEY!,
secretAccessKey: process.env.DO_SECRET_KEY!,
publicUrl: 'https://my-space.nyc3.cdn.digitaloceanspaces.com'
},
// MinIO (self-hosted)
minio: {
driver: 'minio',
bucket: 'uploads',
endpoint: 'http://minio:9000',
accessKey: process.env.MINIO_ACCESS_KEY!,
secretKey: process.env.MINIO_SECRET_KEY!,
useSSL: false
}
}
});StorageService API
const storage = storagePlugin.Service;
// Use default disk
await storage.put('avatars/user-123.jpg', buffer);
await storage.put('documents/report.pdf', stream);
// Use specific disk
await storage.disk('s3').put('backups/data.zip', buffer);
// Read file
const content = await storage.get('avatars/user-123.jpg');
const stream = await storage.getStream('videos/large.mp4');
// Delete file
await storage.delete('temp/file.txt');
await storage.deleteMany(['temp/a.txt', 'temp/b.txt']);
// Check existence
const exists = await storage.exists('avatars/user-123.jpg');
// List files
const files = await storage.list('documents/');
const files = await storage.list('documents/', { recursive: true });
// Get file info
const meta = await storage.metadata('file.pdf');
// { size: 1024, mimeType: 'application/pdf', lastModified: Date }
// URLs
const publicUrl = await storage.url('public/image.jpg');
const signedUrl = await storage.signedUrl('private/doc.pdf', { expiresIn: 3600 });
// Copy/Move
await storage.copy('source.jpg', 'dest.jpg');
await storage.move('old-path.jpg', 'new-path.jpg');
// Disk operations
await storage.disk('s3').put('file.txt', content);
const s3Files = await storage.disk('s3').list('/');Actions
The plugin registers the following actions:
| Action | Description |
|--------|-------------|
| uploadFile | Upload a file (stream) |
| downloadFile | Download a file |
| deleteFile | Delete a file |
| listFiles | List files in directory |
| getFileUrl | Get public/signed URL |
| getFileMetadata | Get file information |
Using in Actions
import { Action, ActionProvider, Stream, Input } from '@teliagen/commons';
import { Readable } from 'stream';
@ActionProvider('files', 'FileActions')
class FileActions {
@Action('uploadAvatar', { transport: 'stream' })
async uploadAvatar(
@User() user: any,
@Stream() stream: Readable,
@Input() input: { filename: string }
) {
const storage = storagePlugin.Service;
const path = `avatars/${user.id}/${input.filename}`;
await storage.put(path, stream);
const url = await storage.url(path);
return { path, url };
}
@Action('getDocument')
async getDocument(@Input() input: { path: string }) {
const storage = storagePlugin.Service;
if (!await storage.exists(input.path)) {
throw new Error('File not found');
}
const signedUrl = await storage.signedUrl(input.path, {
expiresIn: 3600 // 1 hour
});
return { url: signedUrl };
}
}File Upload Helpers
// With multipart/form-data
@Action('uploadFiles', { transport: 'form-data' })
async uploadFiles(@Input() input: { files: UploadedFile[] }) {
const storage = storagePlugin.Service;
const results = [];
for (const file of input.files) {
const path = `uploads/${Date.now()}-${file.originalname}`;
await storage.put(path, file.buffer);
results.push({ path, size: file.size });
}
return results;
}Path Helpers
import { StoragePlugin } from '@teliagen/plugin-storage';
// Generate unique paths
const path = storagePlugin.helpers.uniquePath('avatars', 'jpg');
// avatars/550e8400-e29b-41d4-a716-446655440000.jpg
const path = storagePlugin.helpers.datePath('documents', 'pdf');
// documents/2024/01/15/550e8400-e29b-41d4-a716-446655440000.pdf
// Sanitize filenames
const safe = storagePlugin.helpers.sanitize('My File (1).pdf');
// my-file-1.pdfEvents
import { EventBus } from '@teliagen/commons';
EventBus.on('storage:uploaded', async ({ disk, path, size }) => {
console.log(`File uploaded to ${disk}: ${path} (${size} bytes)`);
});
EventBus.on('storage:deleted', async ({ disk, path }) => {
console.log(`File deleted from ${disk}: ${path}`);
});Requirements
- Node.js >= 18.0.0
- @teliagen/server >= 0.1.0
Related Packages
- @teliagen/plugin-mail – Email sending
Documentation
For full documentation, visit docs.teliagen.org.
License
MIT © Teliagen Contributors
