Core interfaces for Medusa e-commerce framework service implementations
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Interface for file storage service implementations providing upload and delete operations for handling file assets across different storage providers.
Deprecation Notice: Use AbstractFileService from @medusajs/medusa instead.
Type checking and identification methods for file services.
/**
* Static property identifying this as a file service
*/
static _isFileService: boolean;
/**
* Checks if an object is a file service
* @param {object} obj - Object to check
* @returns {boolean} True if obj is a file service
*/
static isFileService(obj: object): boolean;Abstract methods that must be implemented by child classes for file storage operations.
/**
* Uploads files to the storage provider
* @returns {any} Upload result
* @throws {Error} If not overridden by child class
*/
upload(): any;
/**
* Deletes files from the storage provider
* @returns {any} Deletion result
* @throws {Error} If not overridden by child class
*/
delete(): any;import { FileService } from "medusa-interfaces";
import fs from "fs";
import path from "path";
class LocalFileService extends FileService {
constructor(options) {
super();
this.uploadDir = options.upload_dir || "./uploads";
this.baseUrl = options.base_url || "http://localhost:9000";
}
async upload(files) {
const uploadedFiles = [];
// Handle single file or array of files
const fileArray = Array.isArray(files) ? files : [files];
for (const file of fileArray) {
// Generate unique filename
const filename = `${Date.now()}-${file.originalname}`;
const filepath = path.join(this.uploadDir, filename);
// Ensure upload directory exists
if (!fs.existsSync(this.uploadDir)) {
fs.mkdirSync(this.uploadDir, { recursive: true });
}
// Write file to disk
fs.writeFileSync(filepath, file.buffer);
uploadedFiles.push({
url: `${this.baseUrl}/uploads/${filename}`,
key: filename,
originalname: file.originalname,
size: file.size,
mimetype: file.mimetype
});
}
return uploadedFiles.length === 1 ? uploadedFiles[0] : uploadedFiles;
}
async delete(fileKey) {
try {
const filepath = path.join(this.uploadDir, fileKey);
if (fs.existsSync(filepath)) {
fs.unlinkSync(filepath);
return { success: true, key: fileKey };
} else {
throw new Error(`File not found: ${fileKey}`);
}
} catch (error) {
throw new Error(`Failed to delete file: ${error.message}`);
}
}
}
// AWS S3 implementation example
class S3FileService extends FileService {
constructor(options) {
super();
this.s3Client = new AWS.S3({
accessKeyId: options.access_key_id,
secretAccessKey: options.secret_access_key,
region: options.region
});
this.bucket = options.bucket;
}
async upload(files) {
const uploadedFiles = [];
const fileArray = Array.isArray(files) ? files : [files];
for (const file of fileArray) {
const key = `${Date.now()}-${file.originalname}`;
const uploadParams = {
Bucket: this.bucket,
Key: key,
Body: file.buffer,
ContentType: file.mimetype,
ACL: 'public-read'
};
const result = await this.s3Client.upload(uploadParams).promise();
uploadedFiles.push({
url: result.Location,
key: result.Key,
originalname: file.originalname,
size: file.size,
mimetype: file.mimetype
});
}
return uploadedFiles.length === 1 ? uploadedFiles[0] : uploadedFiles;
}
async delete(fileKey) {
try {
const deleteParams = {
Bucket: this.bucket,
Key: fileKey
};
await this.s3Client.deleteObject(deleteParams).promise();
return { success: true, key: fileKey };
} catch (error) {
throw new Error(`Failed to delete file from S3: ${error.message}`);
}
}
}File services are typically used in Medusa for:
Basic Usage Pattern:
// In a Medusa service or API route
class ProductService {
constructor({ fileService }) {
this.fileService_ = fileService;
}
async uploadProductImages(productId, imageFiles) {
// Upload images using the file service
const uploadedImages = await this.fileService_.upload(imageFiles);
// Store image URLs in product record
await this.updateProduct(productId, {
images: uploadedImages.map(img => img.url)
});
return uploadedImages;
}
async deleteProductImage(imageKey) {
// Delete image file
await this.fileService_.delete(imageKey);
// Remove from product record
// ... update logic
}
}Both abstract methods throw descriptive errors when not implemented:
"upload must be overridden by the child class""delete must be overridden by the child class"Most implementations expect file objects with these properties:
{
originalname: string, // Original filename
buffer: Buffer, // File data
mimetype: string, // MIME type
size: number // File size in bytes
}Upload operations typically return:
{
url: string, // Public URL to access the file
key: string, // Storage identifier/key
originalname: string, // Original filename
size: number, // File size
mimetype: string // MIME type
}Delete operations typically return:
{
success: boolean, // Operation success status
key: string // Key of deleted file
}