UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

554 lines 20.7 kB
import { Alepha, AlephaError, FileLike, KIND, Primitive, Service, Static } from "alepha"; import { FileDetector, FileSystemProvider } from "alepha/system"; import { CryptoProvider } from "alepha/crypto"; import * as fs from "node:fs"; import { S3mini } from "s3mini"; import { R2Bucket } from "@cloudflare/workers-types"; //#region ../../src/bucket/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>; /** * Delete multiple files in one round-trip where the provider supports * batch (R2/S3, up to 1000 per call). Memory/Local fall back to a loop. * * @param bucketName - Container name * @param fileIds - Identifiers of the files to delete */ abstract deleteMany(bucketName: string, fileIds: string[]): Promise<void>; /** * Lists the file identifiers stored in a bucket, like `ls` on a directory. * * This is a flat, unpaginated listing intended for small buckets. Providers * cap the result at their natural page size (~1000 for S3/R2); anything beyond * that is silently dropped. It is NOT a search API — use `alepha/api/files` * when you need querying or pagination. * * @param bucketName - Container name * @return The identifiers of the files in the bucket (empty if none). */ abstract list(bucketName: string): Promise<string[]>; } //#endregion //#region ../../src/bucket/providers/MemoryFileStorageProvider.d.ts interface StoredFile { buffer: Buffer; name: string; type: string; size: number; } declare class MemoryFileStorageProvider implements FileStorageProvider { readonly files: Record<string, StoredFile>; protected readonly alepha: Alepha; protected readonly fileSystem: FileSystemProvider; protected readonly fileDetector: FileDetector; protected readonly crypto: CryptoProvider; 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>; deleteMany(bucketName: string, fileIds: string[]): Promise<void>; list(bucketName: string): Promise<string[]>; /** * In-memory key, tenant-scoped when a tenant is active (`currentTenantAtom`), * mirroring the R2/Local/S3 layout: `{tenantId}/{bucket}/{fileId}`. */ protected key(bucket: string, fileId?: string): string; protected createId(): string; } //#endregion //#region ../../src/bucket/primitives/$bucket.d.ts /** * Creates a bucket primitive for file storage and management with configurable validation. * * Provides a comprehensive file storage system that handles uploads, downloads, validation, * and management across multiple storage backends with MIME type and size limit controls. * * **Key Features** * - Multi-provider support (filesystem, cloud storage, in-memory) * - Automatic MIME type and file size validation * - Event integration for file operations monitoring * - Flexible per-bucket and per-operation configuration * - Smart file type and size detection * * **Common Use Cases** * - User profile pictures and document uploads * - Product images and media management * - Document storage and retrieval systems * * @example * ```ts * class MediaService { * images = $bucket({ * name: "user-images", * mimeTypes: ["image/jpeg", "image/png", "image/gif"], * maxSize: 5 // 5MB limit * }); * * documents = $bucket({ * name: "documents", * mimeTypes: ["application/pdf", "text/plain"], * maxSize: 50 // 50MB limit * }); * * async uploadProfileImage(file: FileLike, userId: string): Promise<string> { * const fileId = await this.images.upload(file); * await this.userService.updateProfileImage(userId, fileId); * return fileId; * } * * async downloadDocument(documentId: string): Promise<FileLike> { * return await this.documents.download(documentId); * } * * async deleteDocument(documentId: string): Promise<void> { * await this.documents.delete(documentId); * } * } * ``` */ declare const $bucket: { (options: BucketPrimitiveOptions): BucketPrimitive; [KIND]: typeof BucketPrimitive; }; interface BucketPrimitiveOptions 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 BucketPrimitive extends Primitive<BucketPrimitiveOptions> { readonly provider: FileStorageProvider | MemoryFileStorageProvider; protected readonly fileSystem: FileSystemProvider; 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>; /** * Delete many files in one round-trip when the underlying provider supports * batch (R2/S3 up to 1000 keys per call). Emits one `bucket:file:deleted` * event per id unless `skipHook` is set. */ deleteMany(fileIds: string[], skipHook?: boolean): Promise<void>; /** * Checks if a file exists in the bucket. */ exists(fileId: string): Promise<boolean>; /** * Lists the file identifiers stored in the bucket, like `ls` on a directory. * * This is a flat, unpaginated listing meant for small buckets — the result is * capped at the provider's natural page size (~1000 for S3/R2) and anything * beyond that is silently dropped. It is NOT a search API; reach for * `alepha/api/files` when you need querying or pagination. */ list(): Promise<string[]>; /** * 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/bucket/errors/FileNotFoundError.d.ts declare class FileNotFoundError extends AlephaError { readonly status = 404; } //#endregion //#region ../../src/bucket/providers/LocalFileStorageProvider.d.ts /** * Local file storage configuration atom */ declare const localFileStorageOptions: import("alepha").Atom<import("zod").ZodObject<{ storagePath: import("zod").ZodString; }, import("zod/v4/core").$strip>, "alepha.bucket.local.options">; type LocalFileStorageProviderOptions = Static<typeof localFileStorageOptions.schema>; declare module "alepha" { interface State { [localFileStorageOptions.key]: LocalFileStorageProviderOptions; } } declare class LocalFileStorageProvider implements FileStorageProvider { protected readonly alepha: Alepha; protected readonly log: import("alepha/logger").Logger; protected readonly fileDetector: FileDetector; protected readonly fileSystemProvider: FileSystemProvider; protected readonly crypto: CryptoProvider; protected readonly options: Readonly<{ storagePath: string; }>; protected get storagePath(): string; protected readonly onConfigure: import("alepha").HookPrimitive<"configure">; protected readonly onStart: import("alepha").HookPrimitive<"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>; deleteMany(bucketName: string, fileIds: string[]): Promise<void>; list(bucketName: string): Promise<string[]>; protected stat(bucket: string, fileId: string): Promise<fs.Stats>; protected createId(mimeType: string): string; protected path(bucket: string, fileId?: string): string; protected isErrorNoEntry(error: unknown): boolean; } //#endregion //#region ../../src/bucket/providers/R2FileStorageProvider.d.ts /** * Cloudflare R2 storage provider. * * Uses a single R2 bucket binding for all $bucket primitives. * Files are organized as: {APP_NAME}/{bucketName}/{fileId} * * **Required environment variables:** * - `R2_BUCKET_NAME` - The actual R2 bucket name in Cloudflare * * **Optional (uses core Alepha env):** * - `APP_NAME` - Prefix for all files (for multi-app setups sharing one R2 bucket) * * @example * ```bash * # .env * APP_NAME=myapp # optional, used as prefix * R2_BUCKET_NAME=storage * ``` * * @example * ```toml * # wrangler.toml - automatically generated by alepha build * [[r2_buckets]] * binding = "R2" * bucket_name = "storage" * ``` * * @example * ```ts * // Define buckets with validation rules * const avatars = $bucket({ * name: "avatars", * maxFileSize: 5 * 1024 * 1024, * allowedMimeTypes: ["image/*"], * }); * * const documents = $bucket({ * name: "documents", * maxFileSize: 50 * 1024 * 1024, * allowedMimeTypes: ["application/pdf"], * }); * * // Files stored at: myapp/avatars/uuid.png, myapp/documents/uuid.pdf * ``` */ declare class R2FileStorageProvider implements FileStorageProvider { protected readonly alepha: Alepha; protected readonly log: import("alepha/logger").Logger; protected readonly crypto: CryptoProvider; protected readonly env: { R2_BUCKET_NAME: string; }; protected r2?: R2Bucket; /** * Get the R2 bucket name from environment. */ get bucketName(): string; /** * Get the optional prefix from APP_NAME environment variable. * Used for multi-app setups sharing the same R2 bucket. */ get prefix(): string | undefined; protected readonly onStart: import("alepha").HookPrimitive<"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>; deleteMany(bucketName: string, fileIds: string[]): Promise<void>; list(bucketName: string): Promise<string[]>; /** * Build the full R2 key: {prefix}/{tenantId}/{bucketName}/{fileId} * * When a tenant is active on the current request/job (`currentTenantAtom`), * its id is inserted as a directory so a pooled multi-tenant worker keeps * each tenant's objects isolated. No tenant → the historical * `{prefix}/{bucketName}/{fileId}` layout is unchanged. */ protected key(bucketName: string, fileId: string): string; protected getR2(): R2Bucket; protected createId(filename: string): string; } //#endregion //#region ../../src/bucket/providers/S3FileStorageProvider.d.ts declare const envSchema: import("zod").ZodObject<{ S3_ENDPOINT: import("zod").ZodString; S3_REGION: import("zod").ZodOptional<import("zod").ZodString>; S3_ACCESS_KEY_ID: import("zod").ZodString; S3_SECRET_ACCESS_KEY: import("zod").ZodString; }, import("zod/v4/core").$strip>; declare module "alepha" { interface Env extends Partial<Static<typeof envSchema>> {} } /** * S3-compatible file storage provider for Node.js. * * Backed by `s3mini` (zero-dep, ~20 KB). Works with AWS S3, Cloudflare R2, * MinIO, DigitalOcean Spaces, Backblaze B2, and any other S3-compatible service. * * Uses path-style addressing (`<endpoint>/<bucket>`). */ declare class S3FileStorageProvider implements FileStorageProvider { protected readonly log: import("alepha/logger").Logger; protected readonly env: { S3_ENDPOINT: string; S3_REGION?: string | undefined; S3_ACCESS_KEY_ID: string; S3_SECRET_ACCESS_KEY: string; }; protected readonly alepha: Alepha; protected readonly fileSystem: FileSystemProvider; protected readonly fileDetector: FileDetector; protected readonly crypto: CryptoProvider; protected readonly clients: Map<string, S3mini>; /** * Convert bucket name to S3-compatible format. * S3 bucket names must be lowercase, 3-63 characters, no underscores. */ convertName(name: string): string; protected getClient(bucketName: string): S3mini; protected readonly onStart: import("alepha").HookPrimitive<"start">; /** * Object key, tenant-scoped when a tenant is active (`currentTenantAtom`): * `{tenantId}/{fileId}` within the per-bucket S3 bucket. No tenant → `{fileId}`. */ protected key(fileId: string): string; protected createId(mimeType: string): string; 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>; list(bucketName: string): Promise<string[]>; deleteMany(bucketName: string, fileIds: string[]): Promise<void>; } //#endregion //#region ../../src/bucket/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: BucketPrimitive; options: BucketFileOptions; }; /** * Triggered when a file is deleted from a bucket. */ "bucket:file:deleted": { id: string; bucket: BucketPrimitive; }; /** * Triggered when a file is downloaded from a bucket. */ "bucket:file:downloaded": { id: string; file: FileLike; bucket: BucketPrimitive; }; } } /** * Unified file storage abstraction across multiple backends. * * **Features:** * - File storage buckets with constraints * - Unified API across all storage backends * - MIME type validation * - File size limits * - Upload/download/delete operations * - TTL-based file expiration * - Providers: Memory (testing), Local filesystem, AWS S3 / Cloudflare R2 / MinIO, Azure Blob Storage, Vercel Blob * * @module alepha.bucket */ declare const AlephaBucket: import("alepha").Service<import("alepha").Module>; //#endregion export { $bucket, AlephaBucket, BucketFileOptions, BucketPrimitive, BucketPrimitiveOptions, FileNotFoundError, FileStorageProvider, LocalFileStorageProvider, LocalFileStorageProviderOptions, MemoryFileStorageProvider, R2FileStorageProvider, S3FileStorageProvider, localFileStorageOptions }; //# sourceMappingURL=index.d.ts.map