/** * CloudflareR2Adapter - Cloudflare R2 storage adapter * * Cloudflare R2 is an S3-compatible object storage service with zero egress fees. * This adapter uses the AWS S3 SDK for compatibility. * * Features: * - S3-compatible API * - Zero egress fees * - Global edge network * - Automatic presigned URLs * - Multipart upload support (future) * - Custom domain support * - Soft delete with metadata * * Configuration: * - accountId: Cloudflare account ID * - accessKeyId: R2 API token ID * - secretAccessKey: R2 API token secret * - bucket: R2 bucket name * - region: Optional (defaults to 'auto') * - endpoint: Optional custom endpoint * - publicDomain: Optional public URL domain for public files * * @example * ```ts * const adapter = new CloudflareR2Adapter({ * name: 'r2-storage', * type: STORAGE_ADAPTER_TYPE.CloudflareR2, * credentials: { * accountId: process.env.R2_ACCOUNT_ID!, * accessKeyId: process.env.R2_ACCESS_KEY_ID!, * secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!, * }, * bucket: process.env.R2_BUCKET_NAME!, * publicDomain: 'https://cdn.example.com', // Optional * }); * ``` */ import { BaseStorageAdapter } from '../base/BaseStorageAdapter'; import type { CloudflareR2Config, UploadParams, UploadResult, DownloadParams, DownloadResult, DeleteParams, FileDeleteResult, PresignedUrlOptions, PresignedUrlResult, AdapterHealthCheck, FileMetadata, BucketConfiguration, StorageListBucketsResult, StorageBucketInfo, StorageUpdateBucketParams, StorageListFilesParams, StorageListFilesResult, StorageMoveFileParams, StorageCopyFileParams, StorageReplaceFileParams, StorageSignedUploadUrlParams, StorageSignedUploadUrlResult, StoragePublicUrlParams, BucketDeletionResult } from '@plyaz/types/storage'; import { BUCKET_PURPOSE } from '@plyaz/types/storage'; import { Readable } from 'stream'; /** * CloudflareR2Adapter - Cloudflare R2 storage adapter */ export declare class CloudflareR2Adapter extends BaseStorageAdapter { private readonly s3Client; private readonly bucket?; private readonly accountId; private readonly publicDomain?; private readonly region; private readonly endpoint; private readonly uploadMetadataStore; private readonly validation?; constructor(config: CloudflareR2Config); /** * Upload a file to Cloudflare R2 */ upload(params: UploadParams): Promise; /** * Download a file from Cloudflare R2 */ download(params: DownloadParams): Promise; /** * Delete a file from Cloudflare R2 */ delete(params: DeleteParams): Promise; /** * Generate a presigned URL for temporary file access */ getSignedUrl(options: PresignedUrlOptions): Promise; /** * Check if adapter is available and properly configured */ isAvailable(): boolean; /** * Perform health check on R2 storage */ healthCheck(): Promise; /** * Get file metadata without downloading the file */ getFileMetadata(fileId: string, bucket?: string): Promise; /** * Restore a soft-deleted file */ restoreFile(fileId: string, bucket?: string): Promise; /** * Perform soft delete by updating metadata */ private softDelete; /** * Generate file key/path for R2 storage */ protected generateFileKey(params: UploadParams): string; /** * Convert Readable stream to Buffer */ protected streamToBuffer(stream: Readable): Promise; /** * Map S3/R2 errors to StoragePackageError */ private mapS3Error; /** * Translate bucket purpose to R2-specific bucket name * * Pattern: plyaz-{env}-{region}-{purpose} * Enterprise with org: plyaz-{env}-{region}-{tier}-{org}-{purpose} * * Examples: * - COMPLIANCE → "plyaz-prod-weur-records-vault" * - MEDIA_IMAGES → "plyaz-prod-weur-media-images" * - COMPLIANCE (enterprise, acme-corp) → "plyaz-prod-weur-enterprise-acme-corp-records-vault" */ translateBucketPurpose(purpose: BUCKET_PURPOSE, config: BucketConfiguration): string; /** * Create R2 bucket if it doesn't exist * Idempotent: Returns success if bucket already exists */ createBucket(bucketName: string, purpose: BUCKET_PURPOSE): Promise; /** * Delete buckets by name * Useful for cleanup in tests or migrations */ deleteBuckets(bucketNames: string[]): Promise; /** * Get purpose-specific bucket suffix */ private getPurposeSuffix; /** * Sanitize region code for bucket naming */ private sanitizeRegion; /** * Sanitize name for bucket naming (lowercase, alphanumeric, hyphens) */ private sanitizeName; /** * List all buckets */ listBuckets(): Promise; /** * Get bucket details */ getBucket(bucketName: string): Promise; /** * Update bucket settings * Note: R2/S3 has limited bucket update capabilities via SDK */ updateBucket(params: StorageUpdateBucketParams): Promise; /** * List files in a bucket */ listFiles(params: StorageListFilesParams): Promise; /** * Move a file (implemented as copy + delete) */ moveFile(params: StorageMoveFileParams): Promise; /** * Copy a file */ copyFile(params: StorageCopyFileParams): Promise; /** * Replace an existing file */ replaceFile(params: StorageReplaceFileParams): Promise; /** * Create a signed upload URL */ createSignedUploadUrl(params: StorageSignedUploadUrlParams): Promise; /** * Get public URL for a file */ getPublicUrl(params: StoragePublicUrlParams): Promise; /** * Initialize chunked/multipart upload * Returns uploadId and presigned URLs for direct client uploads */ /** * Upload large file using chunked/multipart upload * Handles entire chunked upload flow using S3 SDK */ uploadChunked(params: UploadParams): Promise; /** * Helper: Handle bucket creation error (ignore if already exists) */ private handleBucketCreationError; /** * Helper: Delete all objects from a bucket (batch operation) */ private deleteBucketObjects; /** * Helper: Delete a batch of objects from a bucket */ private deleteObjectsBatch; /** * Helper: Check if upload was aborted */ private checkUploadAborted; /** * Helper: Extract chunk from buffer */ private extractChunk; /** * Helper: Upload a single chunk part */ private uploadChunkPart; /** * Get file size limit for bucket purpose * Used by StorageService for validation */ getFileSizeLimit(purpose: BUCKET_PURPOSE): number | undefined; /** * Get allowed MIME types for bucket purpose * Used by StorageService for validation */ getAllowedMimeTypes(purpose: BUCKET_PURPOSE): string[] | undefined; }