@profullstack/storage-service
Version:
Flexible storage service abstraction for file storage, retrieval, and metadata management
393 lines (365 loc) • 11 kB
JavaScript
/**
* 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;