UNPKG

@profullstack/storage-service

Version:

Flexible storage service abstraction for file storage, retrieval, and metadata management

393 lines (365 loc) 11 kB
/** * S3 adapter for @profullstack/storage-service */ /** * S3 Storage Adapter */ export class S3Adapter { /** * Create a new S3 adapter * @param {Object} options - S3 options * @param {Object} options.s3Client - Initialized S3 client * @param {string} options.region - AWS region * @param {string} options.endpoint - Custom endpoint for S3-compatible services * @param {string} options.accessKeyId - AWS access key ID * @param {string} options.secretAccessKey - AWS secret access key * @param {string} options.defaultBucket - Default bucket name */ constructor(options = {}) { // Check if we have a client or credentials if (!options.s3Client && (!options.accessKeyId || !options.secretAccessKey)) { throw new Error('S3 client or credentials are required'); } // Store options this.options = { region: options.region || 'us-east-1', endpoint: options.endpoint || null, defaultBucket: options.defaultBucket || 'default', ...options }; // Initialize S3 client if not provided this.s3 = options.s3Client || this._createS3Client(); } /** * Create S3 client * @private * @returns {Object} S3 client */ _createS3Client() { // This is a placeholder for actual S3 client initialization // In a real implementation, you would use the AWS SDK // For example: // // import { S3Client } from '@aws-sdk/client-s3'; // // return new S3Client({ // region: this.options.region, // endpoint: this.options.endpoint, // credentials: { // accessKeyId: this.options.accessKeyId, // secretAccessKey: this.options.secretAccessKey // } // }); throw new Error('S3 client initialization requires the AWS SDK. Please provide an initialized S3 client.'); } /** * Create a bucket * @param {string} bucketName - Bucket name * @param {Object} options - Bucket options * @returns {Promise<Object>} - Bucket information */ async createBucket(bucketName, options = {}) { try { // Create bucket in S3 // In a real implementation, you would use the AWS SDK // For example: // // import { CreateBucketCommand } from '@aws-sdk/client-s3'; // // const command = new CreateBucketCommand({ // Bucket: bucketName, // ACL: options.public ? 'public-read' : 'private' // }); // // await this.s3.send(command); return { name: bucketName, createdAt: new Date().toISOString(), ...options }; } catch (error) { throw error; } } /** * List all buckets * @returns {Promise<Array>} - List of buckets */ async listBuckets() { try { // List buckets in S3 // In a real implementation, you would use the AWS SDK // For example: // // import { ListBucketsCommand } from '@aws-sdk/client-s3'; // // const command = new ListBucketsCommand({}); // const response = await this.s3.send(command); // // return response.Buckets.map(bucket => ({ // name: bucket.Name, // createdAt: bucket.CreationDate.toISOString() // })); return []; } catch (error) { throw error; } } /** * Delete a bucket * @param {string} bucketName - Bucket name * @param {Object} options - Delete options * @returns {Promise<boolean>} - Whether the bucket was deleted */ async deleteBucket(bucketName, options = {}) { try { // Delete bucket in S3 // In a real implementation, you would use the AWS SDK // For example: // // import { DeleteBucketCommand } from '@aws-sdk/client-s3'; // // // If force option is true, delete all objects first // if (options.force) { // await this._emptyBucket(bucketName); // } // // const command = new DeleteBucketCommand({ // Bucket: bucketName // }); // // await this.s3.send(command); return true; } catch (error) { throw error; } } /** * Upload a file * @param {Object} options - Upload options * @param {string} options.bucketName - Bucket name * @param {string} options.path - File path * @param {Buffer|Blob|string} options.data - File data * @param {string} options.contentType - Content type * @param {Object} options.metadata - File metadata * @param {boolean} options.upsert - Whether to overwrite existing file * @param {boolean} options.public - Whether the file is public * @returns {Promise<Object>} - Upload result */ async uploadFile(options) { try { const { bucketName, path: filePath, data, contentType, metadata = {}, upsert = false, public: isPublic = false } = options; // Upload file to S3 // In a real implementation, you would use the AWS SDK // For example: // // import { PutObjectCommand } from '@aws-sdk/client-s3'; // // const command = new PutObjectCommand({ // Bucket: bucketName, // Key: filePath, // Body: data, // ContentType: contentType, // Metadata: metadata, // ACL: isPublic ? 'public-read' : 'private' // }); // // await this.s3.send(command); // Generate URL const url = isPublic ? this._getPublicUrl(bucketName, filePath) : null; return { path: filePath, url, contentType, size: data.length || (data.size || 0), metadata }; } catch (error) { throw error; } } /** * Download a file * @param {Object} options - Download options * @param {string} options.bucketName - Bucket name * @param {string} options.path - File path * @param {string} options.responseType - Response type * @returns {Promise<Object>} - Download result with data and metadata */ async downloadFile(options) { try { const { bucketName, path: filePath, responseType = 'arraybuffer' } = options; // Download file from S3 // In a real implementation, you would use the AWS SDK // For example: // // import { GetObjectCommand } from '@aws-sdk/client-s3'; // // const command = new GetObjectCommand({ // Bucket: bucketName, // Key: filePath // }); // // const response = await this.s3.send(command); // // // Convert to requested response type // let data; // if (responseType === 'arraybuffer') { // data = await response.Body.transformToByteArray(); // } else if (responseType === 'text') { // data = await response.Body.transformToString(); // } else if (responseType === 'json') { // const text = await response.Body.transformToString(); // data = JSON.parse(text); // } else if (responseType === 'stream') { // data = response.Body; // } return { data: Buffer.from([]), metadata: {}, contentType: 'application/octet-stream', size: 0 }; } catch (error) { throw error; } } /** * Get file information * @param {Object} options - File info options * @param {string} options.bucketName - Bucket name * @param {string} options.path - File path * @returns {Promise<Object>} - File information */ async getFileInfo(options) { try { const { bucketName, path: filePath } = options; // Get file info from S3 // In a real implementation, you would use the AWS SDK // For example: // // import { HeadObjectCommand } from '@aws-sdk/client-s3'; // // const command = new HeadObjectCommand({ // Bucket: bucketName, // Key: filePath // }); // // const response = await this.s3.send(command); return { path: filePath, url: this._getPublicUrl(bucketName, filePath), size: 0, contentType: 'application/octet-stream', metadata: {}, createdAt: new Date().toISOString(), modifiedAt: new Date().toISOString() }; } catch (error) { throw error; } } /** * List files in a bucket * @param {Object} options - List options * @param {string} options.bucketName - Bucket name * @param {string} options.prefix - Path prefix * @param {number} options.limit - Maximum number of files * @param {string} options.cursor - Pagination cursor * @param {string} options.sortBy - Sort field * @param {boolean} options.sortDescending - Sort direction * @returns {Promise<Object>} - List result */ async listFiles(options) { try { const { bucketName, prefix = '', limit = 100, cursor = null } = options; // List files in S3 // In a real implementation, you would use the AWS SDK // For example: // // import { ListObjectsV2Command } from '@aws-sdk/client-s3'; // // const command = new ListObjectsV2Command({ // Bucket: bucketName, // Prefix: prefix, // MaxKeys: limit, // ContinuationToken: cursor // }); // // const response = await this.s3.send(command); // // const files = await Promise.all( // response.Contents.map(async (item) => { // return { // path: item.Key, // url: this._getPublicUrl(bucketName, item.Key), // size: item.Size, // contentType: 'application/octet-stream', // metadata: {}, // createdAt: new Date().toISOString(), // modifiedAt: item.LastModified.toISOString() // }; // }) // ); // // return { // files, // cursor: response.NextContinuationToken // }; return { files: [], cursor: null }; } catch (error) { throw error; } } /** * Get a public URL for a file * @param {string} bucketName - Bucket name * @param {string} filePath - File path * @returns {string|null} - Public URL * @private */ _getPublicUrl(bucketName, filePath) { // Generate public URL // In a real implementation, you would use the AWS SDK or construct the URL // For example: // // if (this.options.endpoint) { // return `${this.options.endpoint}/${bucketName}/${filePath}`; // } else { // return `https://${bucketName}.s3.${this.options.region}.amazonaws.com/${filePath}`; // } return null; } } /** * Create an S3 adapter * @param {Object} options - S3 options * @returns {S3Adapter} S3 adapter */ export function createS3Adapter(options = {}) { return new S3Adapter(options); } export default createS3Adapter;