alepha
Version:
Alepha is a convention-driven TypeScript framework for building robust, end-to-end type-safe applications, from serverless APIs to full-stack React apps.
507 lines (506 loc) • 18.4 kB
TypeScript
import * as _alepha_core1 from "alepha";
import { Alepha, AlephaError, Descriptor, FileLike, KIND, Service } from "alepha";
import * as fs from "node:fs";
import * as _alepha_logger0 from "alepha/logger";
import * as typebox0 from "typebox";
//#region src/providers/FileStorageProvider.d.ts
declare abstract class FileStorageProvider {
/**
* Uploads a file to the storage.
*
* @param bucketName - Container name
* @param file - File to upload
* @param fileId - Optional file identifier. If not provided, a unique ID will be generated.
* @return The identifier of the uploaded file.
*/
abstract upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
/**
* Downloads a file from the storage.
*
* @param bucketName - Container name
* @param fileId - Identifier of the file to download
* @return The downloaded file as a FileLike object.
*/
abstract download(bucketName: string, fileId: string): Promise<FileLike>;
/**
* Check if fileId exists in the storage bucket.
*
* @param bucketName - Container name
* @param fileId - Identifier of the file to stream
* @return True is the file exists, false otherwise.
*/
abstract exists(bucketName: string, fileId: string): Promise<boolean>;
/**
* Delete permanently a file from the storage.
*
* @param bucketName - Container name
* @param fileId - Identifier of the file to delete
*/
abstract delete(bucketName: string, fileId: string): Promise<void>;
}
//#endregion
//#region src/providers/MemoryFileStorageProvider.d.ts
declare class MemoryFileStorageProvider implements FileStorageProvider {
readonly files: Record<string, FileLike>;
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
download(bucketName: string, fileId: string): Promise<FileLike>;
exists(bucketName: string, fileId: string): Promise<boolean>;
delete(bucketName: string, fileId: string): Promise<void>;
protected createId(): string;
}
//#endregion
//#region src/descriptors/$bucket.d.ts
/**
* Creates a bucket descriptor for file storage and management with configurable validation.
*
* This descriptor provides a comprehensive file storage system that handles file uploads,
* downloads, validation, and management across multiple storage backends. It supports
* MIME type validation, size limits, and integrates seamlessly with various storage
* providers for scalable file management in applications.
*
* **Key Features**
*
* - **Multi-Provider Support**: Works with filesystem, cloud storage (S3, Azure), and in-memory providers
* - **File Validation**: Automatic MIME type checking and file size validation
* - **Type Safety**: Full TypeScript support with FileLike interface compatibility
* - **Event Integration**: Emits events for file operations (upload, delete) for monitoring
* - **Flexible Configuration**: Per-bucket and per-operation configuration options
* - **Automatic Detection**: Smart file type and size detection with fallback mechanisms
* - **Error Handling**: Comprehensive error handling with descriptive error messages
*
* **Use Cases**
*
* Perfect for handling file storage requirements across applications:
* - User profile picture and document uploads
* - Product image and media management
* - Document storage and retrieval systems
* - Temporary file handling and processing
* - Content delivery and asset management
* - Backup and archival storage
* - File-based data import/export workflows
*
* @example
* **Basic file upload bucket:**
* ```ts
* import { $bucket } from "alepha/bucket";
*
* class MediaService {
* images = $bucket({
* name: "user-images",
* description: "User uploaded profile images and photos",
* mimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
* maxSize: 5 // 5MB limit
* });
*
* async uploadProfileImage(file: FileLike, userId: string): Promise<string> {
* // File is automatically validated against MIME types and size
* const fileId = await this.images.upload(file);
*
* // Update user profile with new image
* await this.userService.updateProfileImage(userId, fileId);
*
* return fileId;
* }
*
* async getUserProfileImage(userId: string): Promise<FileLike> {
* const user = await this.userService.getUser(userId);
* if (!user.profileImageId) {
* throw new Error('User has no profile image');
* }
*
* return await this.images.download(user.profileImageId);
* }
* }
* ```
*
* @example
* **Document storage with multiple file types:**
* ```ts
* class DocumentManager {
* documents = $bucket({
* name: "company-documents",
* description: "Legal documents, contracts, and reports",
* mimeTypes: [
* "application/pdf",
* "application/msword",
* "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
* "text/plain",
* "text/csv"
* ],
* maxSize: 50 // 50MB for large documents
* });
*
* async uploadDocument(file: FileLike, metadata: { title: string; category: string; userId: string }): Promise<string> {
* try {
* const fileId = await this.documents.upload(file);
*
* // Store document metadata in database
* await this.database.documents.create({
* id: fileId,
* title: metadata.title,
* category: metadata.category,
* uploadedBy: metadata.userId,
* fileName: file.name,
* fileSize: file.size,
* mimeType: file.type,
* uploadedAt: new Date()
* });
*
* console.log(`Document uploaded successfully: ${metadata.title} (${fileId})`);
* return fileId;
*
* } catch (error) {
* console.error(`Failed to upload document: ${metadata.title}`, error);
* throw error;
* }
* }
*
* async downloadDocument(documentId: string, userId: string): Promise<FileLike> {
* // Check permissions
* const document = await this.database.documents.findById(documentId);
* if (!document) {
* throw new Error('Document not found');
* }
*
* const hasAccess = await this.permissionService.canAccessDocument(userId, documentId);
* if (!hasAccess) {
* throw new Error('Insufficient permissions to access document');
* }
*
* // Download and return file
* return await this.documents.download(documentId);
* }
*
* async deleteDocument(documentId: string, userId: string): Promise<void> {
* // Verify ownership or admin privileges
* const document = await this.database.documents.findById(documentId);
* if (document.uploadedBy !== userId && !await this.userService.isAdmin(userId)) {
* throw new Error('Cannot delete document: insufficient permissions');
* }
*
* // Delete from storage and database
* await this.documents.delete(documentId);
* await this.database.documents.delete(documentId);
*
* console.log(`Document deleted: ${document.title} (${documentId})`);
* }
* }
* ```
*/
declare const $bucket: {
(options: BucketDescriptorOptions): BucketDescriptor;
[KIND]: typeof BucketDescriptor;
};
interface BucketDescriptorOptions extends BucketFileOptions {
/**
* File storage provider configuration for the bucket.
*
* Options:
* - **"memory"**: In-memory storage (default for development, lost on restart)
* - **Service<FileStorageProvider>**: Custom provider class (e.g., S3FileStorageProvider, AzureBlobProvider)
* - **undefined**: Uses the default file storage provider from dependency injection
*
* **Provider Selection Guidelines**:
* - **Development**: Use "memory" for fast, simple testing without external dependencies
* - **Production**: Use cloud providers (S3, Azure Blob, Google Cloud Storage) for scalability
* - **Local deployment**: Use filesystem providers for on-premise installations
* - **Hybrid**: Use different providers for different bucket types (temp files vs permanent storage)
*
* **Provider Capabilities**:
* - File persistence and durability guarantees
* - Scalability and performance characteristics
* - Geographic distribution and CDN integration
* - Cost implications for storage and bandwidth
* - Backup and disaster recovery features
*
* @default Uses injected FileStorageProvider
* @example "memory"
* @example S3FileStorageProvider
* @example AzureBlobStorageProvider
*/
provider?: Service<FileStorageProvider> | "memory";
/**
* Unique name identifier for the bucket.
*
* This name is used for:
* - Storage backend organization and partitioning
* - File path generation and URL construction
* - Logging, monitoring, and debugging
* - Access control and permissions management
* - Backup and replication configuration
*
* **Naming Conventions**:
* - Use lowercase with hyphens for consistency
* - Include purpose or content type in the name
* - Avoid spaces and special characters
* - Consider environment prefixes for deployment isolation
*
* If not provided, defaults to the property key where the bucket is declared.
*
* @example "user-avatars"
* @example "product-images"
* @example "legal-documents"
* @example "temp-processing-files"
*/
name?: string;
}
interface BucketFileOptions {
/**
* Human-readable description of the bucket's purpose and contents.
*
* Used for:
* - Documentation generation and API references
* - Developer onboarding and system understanding
* - Monitoring dashboards and admin interfaces
* - Compliance and audit documentation
*
* **Description Best Practices**:
* - Explain what types of files this bucket stores
* - Mention any special handling or processing requirements
* - Include information about retention policies if applicable
* - Note any compliance or security considerations
*
* @example "User profile pictures and avatar images"
* @example "Product catalog images with automated thumbnail generation"
* @example "Legal documents requiring long-term retention"
* @example "Temporary files for data processing workflows"
*/
description?: string;
/**
* Array of allowed MIME types for files uploaded to this bucket.
*
* When specified, only files with these exact MIME types will be accepted.
* Files with disallowed MIME types will be rejected with an InvalidFileError.
*
* **MIME Type Categories**:
* - Images: "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"
* - Documents: "application/pdf", "text/plain", "text/csv"
* - Office: "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
* - Archives: "application/zip", "application/x-tar", "application/gzip"
* - Media: "video/mp4", "audio/mpeg", "audio/wav"
*
* **Security Considerations**:
* - Always validate MIME types for user uploads
* - Be cautious with executable file types
* - Consider using allow-lists rather than deny-lists
* - Remember that MIME types can be spoofed by malicious users
*
* If not specified, all MIME types are allowed (not recommended for user uploads).
*
* @example ["image/jpeg", "image/png"] // Only JPEG and PNG images
* @example ["application/pdf", "text/plain"] // Documents only
* @example ["video/mp4", "video/webm"] // Video files
*/
mimeTypes?: string[];
/**
* Maximum file size allowed in megabytes (MB).
*
* Files larger than this limit will be rejected with an InvalidFileError.
* This helps prevent:
* - Storage quota exhaustion
* - Memory issues during file processing
* - Long upload times and timeouts
* - Abuse of storage resources
*
* **Size Guidelines by File Type**:
* - Profile images: 1-5 MB
* - Product photos: 5-10 MB
* - Documents: 10-50 MB
* - Video files: 50-500 MB
* - Data files: 100-1000 MB
*
* **Considerations**:
* - Consider your storage costs and limits
* - Factor in network upload speeds for users
* - Account for processing requirements (thumbnails, compression)
* - Set reasonable limits based on actual use cases
*
* @default 10 MB
*
* @example 1 // 1MB for small images
* @example 25 // 25MB for documents
* @example 100 // 100MB for media files
*/
maxSize?: number;
}
declare class BucketDescriptor extends Descriptor<BucketDescriptorOptions> {
readonly provider: FileStorageProvider | MemoryFileStorageProvider;
get name(): string;
/**
* Uploads a file to the bucket.
*/
upload(file: FileLike, options?: BucketFileOptions): Promise<string>;
/**
* Delete permanently a file from the bucket.
*/
delete(fileId: string, skipHook?: boolean): Promise<void>;
/**
* Checks if a file exists in the bucket.
*/
exists(fileId: string): Promise<boolean>;
/**
* Downloads a file from the bucket.
*/
download(fileId: string): Promise<FileLike>;
protected $provider(): FileStorageProvider | MemoryFileStorageProvider;
}
interface BucketFileOptions {
/**
* Optional description of the bucket.
*/
description?: string;
/**
* Allowed MIME types.
*/
mimeTypes?: string[];
/**
* Maximum size of the files in the bucket.
*
* @default 10
*/
maxSize?: number;
}
//#endregion
//#region src/errors/FileNotFoundError.d.ts
declare class FileNotFoundError extends AlephaError {
readonly status = 404;
}
//#endregion
//#region src/services/FileMetadataService.d.ts
interface FileMetadata {
name: string;
type: string;
}
/**
* Service for encoding/decoding file metadata in storage streams.
*
* The metadata is stored at the beginning of the file with the following structure:
* - 4-byte header: UInt32BE containing the metadata length
* - N-byte metadata: JSON object containing file metadata (name, type)
* - Remaining bytes: Actual file content
*
* @example
* ```typescript
* const service = new FileMetadataService();
*
* // Encode metadata and content for storage
* const { header, metadata } = service.encodeMetadata({
* name: "document.pdf",
* type: "application/pdf"
* });
*
* // Decode metadata from stored file
* const fileHandle = await open(filePath, 'r');
* const { metadata, contentStart } = await service.decodeMetadata(fileHandle);
* ```
*/
declare class FileMetadataService {
/**
* Length of the header containing metadata size (4 bytes for UInt32BE)
*/
static readonly METADATA_HEADER_LENGTH = 4;
/**
* Encodes file metadata into header and metadata buffers.
*
* @param file - The file or metadata to encode
* @returns Object containing the header buffer and metadata buffer
*/
encodeMetadata(file: FileLike | FileMetadata): {
header: Buffer;
metadata: Buffer;
};
/**
* Decodes file metadata from a file handle.
*
* @param fileHandle - File handle opened for reading
* @returns Object containing the decoded metadata and content start position
*/
decodeMetadata(fileHandle: {
read: (buffer: Buffer, offset: number, length: number, position: number) => Promise<{
bytesRead: number;
}>;
}): Promise<{
metadata: FileMetadata;
contentStart: number;
}>;
/**
* Decodes file metadata from a buffer.
*
* @param buffer - Buffer containing the file with metadata
* @returns Object containing the decoded metadata and content start position
*/
decodeMetadataFromBuffer(buffer: Buffer): {
metadata: FileMetadata;
contentStart: number;
};
/**
* Creates a complete buffer with metadata header, metadata, and content.
*
* @param file - The file to encode
* @param content - The file content as a buffer
* @returns Complete buffer ready for storage
*/
createFileBuffer(file: FileLike | FileMetadata, content: Buffer): Buffer;
}
//#endregion
//#region src/providers/LocalFileStorageProvider.d.ts
declare class LocalFileStorageProvider implements FileStorageProvider {
protected readonly alepha: Alepha;
protected readonly log: _alepha_logger0.Logger;
protected readonly metadataService: FileMetadataService;
options: {
storagePath: string;
};
protected readonly configure: _alepha_core1.HookDescriptor<"start">;
upload(bucketName: string, file: FileLike, fileId?: string): Promise<string>;
download(bucketName: string, fileId: string): Promise<FileLike>;
exists(bucketName: string, fileId: string): Promise<boolean>;
delete(bucketName: string, fileId: string): Promise<void>;
protected stat(bucket: string, fileId: string): Promise<fs.Stats>;
protected createId(): string;
protected path(bucket: string, fileId?: string): string;
protected isErrorNoEntry(error: unknown): boolean;
}
declare const fileMetadataSchema: typebox0.TObject<{
name: typebox0.TString;
type: typebox0.TString;
size: typebox0.TNumber;
}>;
//#endregion
//#region src/index.d.ts
declare module "alepha" {
interface Hooks {
/**
* Triggered when a file is uploaded to a bucket.
* Can be used to perform actions after a file is uploaded, like creating a database record!
*/
"bucket:file:uploaded": {
id: string;
file: FileLike;
bucket: BucketDescriptor;
options: BucketFileOptions;
};
/**
* Triggered when a file is deleted from a bucket.
*/
"bucket:file:deleted": {
id: string;
bucket: BucketDescriptor;
};
}
}
/**
* Provides file storage capabilities through declarative bucket descriptors with support for multiple storage backends.
*
* The bucket module enables unified file operations across different storage systems using the `$bucket` descriptor
* on class properties. It abstracts storage provider differences, offering consistent APIs for local filesystem,
* cloud storage, or in-memory storage for testing environments.
*
* @see {@link $bucket}
* @see {@link FileStorageProvider}
* @module alepha.bucket
*/
declare const AlephaBucket: _alepha_core1.Service<_alepha_core1.Module<{}>>;
//#endregion
export { $bucket, AlephaBucket, BucketDescriptor, BucketDescriptorOptions, BucketFileOptions, FileMetadata, FileMetadataService, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, MemoryFileStorageProvider, fileMetadataSchema };
//# sourceMappingURL=index.d.ts.map